Compare commits
40 Commits
backup
...
5b0a8386ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b0a8386ad | ||
|
|
4b8c54d38d | ||
|
|
cc96802637 | ||
|
|
95811ce3f8 | ||
|
|
4a7a2c30c9 | ||
|
|
8ebbb803e8 | ||
|
|
380978959b | ||
|
|
072369fa17 | ||
|
|
45994a9439 | ||
|
|
3ef872ac8c | ||
|
|
051ef625ba | ||
|
|
07f6451c5a | ||
|
|
979b05f2ca | ||
|
|
aefc653e22 | ||
|
|
4f14918b02 | ||
|
|
f8a33123d0 | ||
|
|
95bb989e07 | ||
|
|
004ac0c696 | ||
|
|
f0642e4587 | ||
|
|
2504fab3e7 | ||
|
|
a4229594bf | ||
|
|
55a254457b | ||
|
|
67670cc380 | ||
|
|
e26a809122 | ||
|
|
b1f20de393 | ||
|
|
c84699fd16 | ||
|
|
46299cb7f2 | ||
|
|
9f40b3f8a0 | ||
|
|
6cb2ebf3c8 | ||
|
|
871fe3a070 | ||
|
|
22ae26d488 | ||
|
|
b785051a89 | ||
|
|
0e58ffb735 | ||
|
|
76656bb6a8 | ||
|
|
113cce73ad | ||
|
|
fb62f076a0 | ||
|
|
6a43435950 | ||
|
|
f71be26ae4 | ||
|
|
d1e4901eee | ||
|
|
fc78ec0813 |
38
.drone.yml
38
.drone.yml
@@ -2,26 +2,27 @@ kind: pipeline
|
|||||||
type: docker
|
type: docker
|
||||||
name: default
|
name: default
|
||||||
|
|
||||||
steps:
|
volumes:
|
||||||
- name: build-dotnet
|
- name: dockersock
|
||||||
image: alpine
|
host:
|
||||||
commands:
|
path: /var/run/docker.sock
|
||||||
- echo "Docker build will handle dotnet publish"
|
|
||||||
|
|
||||||
- name: build-docker
|
steps:
|
||||||
image: plugins/docker
|
- name: docker-build
|
||||||
settings:
|
image: docker:24
|
||||||
registry: 192.168.1.9:3000
|
volumes:
|
||||||
repo: 192.168.1.9:3000/tai/aberwyn/aberwyn
|
- name: dockersock
|
||||||
username:
|
path: /var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
GITEA_USERNAME:
|
||||||
from_secret: gitea_username
|
from_secret: gitea_username
|
||||||
password:
|
GITEA_TOKEN:
|
||||||
from_secret: gitea_token
|
from_secret: gitea_token
|
||||||
dockerfile: Aberwyn/Dockerfile
|
commands:
|
||||||
context: .
|
- export DOCKER_BUILDKIT=1
|
||||||
tags:
|
- docker buildx create --use --driver docker-container || true
|
||||||
- latest
|
- echo "$GITEA_TOKEN" | docker login 192.168.1.9:3000 -u "$GITEA_USERNAME" --password-stdin
|
||||||
insecure: true
|
- docker buildx build --builder default --tag 192.168.1.9:3000/tai/aberwyn/aberwyn:latest --tag 192.168.1.9:3000/tai/aberwyn/aberwyn:${DRONE_COMMIT_SHA:0:7} --cache-from=type=registry,ref=192.168.1.9:3000/tai/aberwyn/aberwyn:buildcache --push -f Aberwyn/Dockerfile .
|
||||||
|
|
||||||
- name: restart-unraid-container
|
- name: restart-unraid-container
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
@@ -37,7 +38,8 @@ steps:
|
|||||||
- docker stop aberwyn || true
|
- docker stop aberwyn || true
|
||||||
- docker rm aberwyn || true
|
- docker rm aberwyn || true
|
||||||
- docker volume create aberwyn_config || true
|
- docker volume create aberwyn_config || true
|
||||||
- docker run -d --name=aberwyn --net=br0 -e TZ=Europe/Berlin -p 80:80 -v aberwyn_config:/app/infrastructure 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
- docker volume create aberwyn_keys || true
|
||||||
|
- docker run -d --name=aberwyn --net=br0 -e TZ=Europe/Berlin -p 80:80 -v aberwyn_config:/app/infrastructure -v aberwyn_keys:/root/.aspnet/DataProtection-Keys 192.168.1.9:3000/tai/aberwyn/aberwyn:latest
|
||||||
|
|
||||||
- name: notify-result
|
- name: notify-result
|
||||||
image: alpine
|
image: alpine
|
||||||
|
|||||||
@@ -52,3 +52,7 @@ steps:
|
|||||||
else
|
else
|
||||||
curl -X POST http://192.168.1.196:8123/api/webhook/aberwyn_update_failed
|
curl -X POST http://192.168.1.196:8123/api/webhook/aberwyn_update_failed
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|||||||
@@ -9,13 +9,6 @@
|
|||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Views\NewFolder\**" />
|
|
||||||
<Content Remove="Views\NewFolder\**" />
|
|
||||||
<EmbeddedResource Remove="Views\NewFolder\**" />
|
|
||||||
<None Remove="Views\NewFolder\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<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="HtmlAgilityPack" Version="1.11.67" />
|
||||||
|
|||||||
@@ -19,32 +19,13 @@
|
|||||||
<span asp-validation-for="Input.UserName" class="text-danger"></span>
|
<span asp-validation-for="Input.UserName" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
|
|
||||||
<label asp-for="Input.Password" class="form-label"></label>
|
<label asp-for="Input.Password" class="form-label"></label>
|
||||||
|
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
|
||||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<label asp-for="Input.RememberMe" class="form-label">
|
|
||||||
<input class="form-check-input" asp-for="Input.RememberMe" />
|
|
||||||
@Html.DisplayNameFor(m => m.Input.RememberMe)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
<button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Aberwyn.Areas.Identity.Pages.Account
|
|||||||
public class InputModel
|
public class InputModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[Display(Name = "Användarnamn")]
|
[Display(Name = "Username")]
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ namespace Aberwyn.Controllers
|
|||||||
var dto = new BudgetDto
|
var dto = new BudgetDto
|
||||||
{
|
{
|
||||||
Id = period.Id,
|
Id = period.Id,
|
||||||
Year = period.Year,
|
Year = period.Year ?? 0,
|
||||||
Month = period.Month,
|
Month = period.Month ?? 0,
|
||||||
Categories = period.Categories
|
Categories = period.Categories
|
||||||
.OrderBy(cat => cat.Order)
|
.OrderBy(cat => cat.Order)
|
||||||
.Select(cat => new BudgetCategoryDto
|
.Select(cat => new BudgetCategoryDto
|
||||||
@@ -61,7 +61,8 @@ namespace Aberwyn.Controllers
|
|||||||
Amount = i.Amount,
|
Amount = i.Amount,
|
||||||
IsExpense = i.IsExpense,
|
IsExpense = i.IsExpense,
|
||||||
IncludeInSummary = i.IncludeInSummary,
|
IncludeInSummary = i.IncludeInSummary,
|
||||||
BudgetItemDefinitionId = i.BudgetItemDefinitionId
|
BudgetItemDefinitionId = i.BudgetItemDefinitionId,
|
||||||
|
PaymentStatus = i.PaymentStatus
|
||||||
}).ToList()
|
}).ToList()
|
||||||
}).ToList()
|
}).ToList()
|
||||||
};
|
};
|
||||||
@@ -74,6 +75,72 @@ namespace Aberwyn.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("byname/{name}")]
|
||||||
|
public async Task<IActionResult> GetBudgetByName(string name)
|
||||||
|
{
|
||||||
|
var period = await _context.BudgetPeriods
|
||||||
|
.Include(p => p.Categories)
|
||||||
|
.ThenInclude(c => c.Items)
|
||||||
|
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == name.ToLower());
|
||||||
|
|
||||||
|
if (period == null)
|
||||||
|
{
|
||||||
|
return Ok(new BudgetDto
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Categories = new List<BudgetCategoryDto>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto = new BudgetDto
|
||||||
|
{
|
||||||
|
Id = period.Id,
|
||||||
|
Name = period.Name,
|
||||||
|
Year = period.Year ?? 0,
|
||||||
|
Month = period.Month ?? 0,
|
||||||
|
Categories = period.Categories
|
||||||
|
.OrderBy(cat => cat.Order)
|
||||||
|
.Select(cat => new BudgetCategoryDto
|
||||||
|
{
|
||||||
|
Id = cat.Id,
|
||||||
|
Name = cat.Name,
|
||||||
|
Color = cat.Color,
|
||||||
|
Items = cat.Items
|
||||||
|
.OrderBy(i => i.Order)
|
||||||
|
.Select(i => new BudgetItemDto
|
||||||
|
{
|
||||||
|
Id = i.Id,
|
||||||
|
Name = i.Name,
|
||||||
|
Amount = i.Amount,
|
||||||
|
IsExpense = i.IsExpense,
|
||||||
|
IncludeInSummary = i.IncludeInSummary,
|
||||||
|
BudgetItemDefinitionId = i.BudgetItemDefinitionId,
|
||||||
|
PaymentStatus = i.PaymentStatus
|
||||||
|
}).ToList()
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPut("updatePaymentStatus")]
|
||||||
|
public IActionResult UpdatePaymentStatus([FromBody] PaymentStatusUpdateDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
return BadRequest("dto is null");
|
||||||
|
|
||||||
|
var item = _context.BudgetItems.Find(dto.ItemId);
|
||||||
|
if (item == null) return NotFound();
|
||||||
|
|
||||||
|
item.PaymentStatus = (PaymentStatus)dto.Status;
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPut("category/{id}")]
|
[HttpPut("category/{id}")]
|
||||||
public async Task<IActionResult> UpdateCategory(int id, [FromBody] BudgetCategoryDto updatedCategory)
|
public async Task<IActionResult> UpdateCategory(int id, [FromBody] BudgetCategoryDto updatedCategory)
|
||||||
{
|
{
|
||||||
@@ -183,11 +250,37 @@ namespace Aberwyn.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreatePeriod([FromBody] BudgetPeriod newPeriod)
|
public async Task<IActionResult> CreatePeriod([FromBody] BudgetPeriod newPeriod)
|
||||||
{
|
{
|
||||||
_context.BudgetPeriods.Add(newPeriod);
|
if (!string.IsNullOrWhiteSpace(newPeriod.Name))
|
||||||
await _context.SaveChangesAsync();
|
{
|
||||||
return CreatedAtAction(nameof(GetBudget), new { year = newPeriod.Year, month = newPeriod.Month }, newPeriod);
|
var existingNamed = await _context.BudgetPeriods
|
||||||
|
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == newPeriod.Name.ToLower());
|
||||||
|
|
||||||
|
if (existingNamed != null)
|
||||||
|
return Conflict("En budget med detta namn finns redan.");
|
||||||
|
|
||||||
|
_context.BudgetPeriods.Add(newPeriod);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return Ok(new { id = newPeriod.Id });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPeriod.Year.HasValue && newPeriod.Month.HasValue)
|
||||||
|
{
|
||||||
|
var existing = await _context.BudgetPeriods
|
||||||
|
.FirstOrDefaultAsync(p => p.Year == newPeriod.Year && p.Month == newPeriod.Month);
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
return Conflict("En budget för denna månad finns redan.");
|
||||||
|
|
||||||
|
_context.BudgetPeriods.Add(newPeriod);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return Ok(new { id = newPeriod.Id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return BadRequest("Varken namn eller år/månad angivet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPut("item/{id}")]
|
[HttpPut("item/{id}")]
|
||||||
public async Task<IActionResult> UpdateItem(int id, [FromBody] BudgetItem updatedItem)
|
public async Task<IActionResult> UpdateItem(int id, [FromBody] BudgetItem updatedItem)
|
||||||
{
|
{
|
||||||
@@ -314,19 +407,12 @@ namespace Aberwyn.Controllers
|
|||||||
return BadRequest("Ogiltig data.");
|
return BadRequest("Ogiltig data.");
|
||||||
|
|
||||||
var period = await _context.BudgetPeriods
|
var period = await _context.BudgetPeriods
|
||||||
.FirstOrDefaultAsync(p => p.Year == newCategoryDto.Year && p.Month == newCategoryDto.Month);
|
.FirstOrDefaultAsync(p => p.Id == newCategoryDto.BudgetPeriodId);
|
||||||
|
|
||||||
if (period == null)
|
if (period == null)
|
||||||
{
|
return NotFound("Kunde inte hitta angiven budgetperiod.");
|
||||||
period = new BudgetPeriod
|
|
||||||
{
|
|
||||||
Year = newCategoryDto.Year,
|
|
||||||
Month = newCategoryDto.Month
|
|
||||||
};
|
|
||||||
_context.BudgetPeriods.Add(period);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 🔁 fortsätt som tidigare…
|
||||||
var definition = await _context.BudgetCategoryDefinitions
|
var definition = await _context.BudgetCategoryDefinitions
|
||||||
.FirstOrDefaultAsync(d => d.Name.ToLower() == newCategoryDto.Name.ToLower());
|
.FirstOrDefaultAsync(d => d.Name.ToLower() == newCategoryDto.Name.ToLower());
|
||||||
|
|
||||||
@@ -356,6 +442,8 @@ namespace Aberwyn.Controllers
|
|||||||
return Ok(new { id = category.Id });
|
return Ok(new { id = category.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpDelete("category/{id}")]
|
[HttpDelete("category/{id}")]
|
||||||
public async Task<IActionResult> DeleteCategory(int id)
|
public async Task<IActionResult> DeleteCategory(int id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,13 +3,31 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Aberwyn.Controllers
|
namespace Aberwyn.Controllers
|
||||||
{
|
{
|
||||||
|
[Authorize(Roles = "Budget")]
|
||||||
public class BudgetController : Controller
|
public class BudgetController : Controller
|
||||||
{
|
{
|
||||||
[Authorize(Roles = "Budget")]
|
[Route("budget/{year:int}/{month:int}")]
|
||||||
|
public IActionResult Index(int year, int month)
|
||||||
|
{
|
||||||
|
ViewBag.Year = year;
|
||||||
|
ViewBag.Month = month;
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("budget/{name}")]
|
||||||
|
public IActionResult Index(string name)
|
||||||
|
{
|
||||||
|
ViewBag.BudgetName = name;
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// För fallback när ingen månad/år anges
|
||||||
|
[Route("budget")]
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
ViewData["HideSidebar"] = true;
|
var now = DateTime.Now;
|
||||||
return View();
|
return RedirectToAction("Index", new { year = now.Year, month = now.Month });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
23
Aberwyn/Controllers/ErrorController.cs
Normal file
23
Aberwyn/Controllers/ErrorController.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Aberwyn.Controllers
|
||||||
|
{
|
||||||
|
public class ErrorController : Controller
|
||||||
|
{
|
||||||
|
[Route("Error/{statusCode}")]
|
||||||
|
public IActionResult HttpStatusCodeHandler(int statusCode)
|
||||||
|
{
|
||||||
|
ViewData["ErrorCode"] = statusCode;
|
||||||
|
return View("Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("Error")]
|
||||||
|
public IActionResult Error()
|
||||||
|
{
|
||||||
|
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
|
||||||
|
ViewData["Exception"] = exceptionFeature?.Error;
|
||||||
|
return View("Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ using System.Linq;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Aberwyn.Services;
|
||||||
|
|
||||||
namespace Aberwyn.Controllers
|
namespace Aberwyn.Controllers
|
||||||
{
|
{
|
||||||
@@ -19,24 +21,28 @@ namespace Aberwyn.Controllers
|
|||||||
private readonly IHostEnvironment _env;
|
private readonly IHostEnvironment _env;
|
||||||
private readonly MenuService _menuService;
|
private readonly MenuService _menuService;
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
|
private readonly PushNotificationService _notificationService;
|
||||||
|
private readonly PizzaNotificationService _pizzaNotifier;
|
||||||
|
|
||||||
public FoodMenuController(MenuService menuService, IConfiguration configuration, IHostEnvironment env, ApplicationDbContext context)
|
public FoodMenuController(MenuService menuService, IConfiguration configuration, IHostEnvironment env, ApplicationDbContext context, PushNotificationService notificationService, PizzaNotificationService pizzaNotificationService)
|
||||||
{
|
{
|
||||||
_menuService = menuService;
|
_menuService = menuService;
|
||||||
|
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_env = env;
|
_env = env;
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_pizzaNotifier = pizzaNotificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult PizzaOrder()
|
public IActionResult PizzaOrder()
|
||||||
{
|
{
|
||||||
var pizzas = _menuService.GetMealsByCategory("Pizza")
|
var meals = _menuService.GetMealsByCategoryName("Pizza", onlyAvailable: true);
|
||||||
.Where(p => p.IsAvailable)
|
Console.WriteLine("Pizzas: ", meals);
|
||||||
.ToList();
|
var dtoList = meals.Select(m => MealListDto.FromMeal(m)).ToList();
|
||||||
|
ViewBag.Pizzas = dtoList;
|
||||||
|
|
||||||
ViewBag.Pizzas = pizzas;
|
|
||||||
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
||||||
|
|
||||||
int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId");
|
int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId");
|
||||||
@@ -53,6 +59,7 @@ namespace Aberwyn.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult EditPizzaOrder(int id)
|
public IActionResult EditPizzaOrder(int id)
|
||||||
{
|
{
|
||||||
@@ -82,11 +89,11 @@ namespace Aberwyn.Controllers
|
|||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult PizzaOrder(string customerName, string pizzaName, string ingredients, int? orderId)
|
public async Task<IActionResult> PizzaOrder(string customerName, string pizzaName, string ingredients, int? orderId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(customerName) || string.IsNullOrWhiteSpace(pizzaName))
|
if (string.IsNullOrWhiteSpace(customerName) || string.IsNullOrWhiteSpace(pizzaName))
|
||||||
{
|
{
|
||||||
TempData["Error"] = "Fyll i b<EFBFBD>de namn och pizza!";
|
TempData["Error"] = "Fyll i både namn och pizza!";
|
||||||
return RedirectToAction("PizzaOrder");
|
return RedirectToAction("PizzaOrder");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +109,10 @@ namespace Aberwyn.Controllers
|
|||||||
order.CustomerName = customerName.Trim();
|
order.CustomerName = customerName.Trim();
|
||||||
order.PizzaName = pizzaName.Trim();
|
order.PizzaName = pizzaName.Trim();
|
||||||
order.IngredientsJson = ingredients;
|
order.IngredientsJson = ingredients;
|
||||||
order.Status = "Unconfirmed";// <20>terst<73>ll status om du vill
|
order.Status = "Unconfirmed";
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
|
|
||||||
TempData["Success"] = $"Din best<EFBFBD>llning har uppdaterats!";
|
TempData["Success"] = $"Din beställning har uppdaterats!";
|
||||||
return RedirectToAction("PizzaOrder");
|
return RedirectToAction("PizzaOrder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,11 +130,11 @@ namespace Aberwyn.Controllers
|
|||||||
_context.PizzaOrders.Add(order);
|
_context.PizzaOrders.Add(order);
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
TempData["ForceShowForm"] = "true";
|
TempData["ForceShowForm"] = "true";
|
||||||
|
await _pizzaNotifier.NotifyPizzaSubscribersAsync(order.PizzaName, order.CustomerName);
|
||||||
|
|
||||||
HttpContext.Session.SetInt32("LastPizzaOrderId", order.Id);
|
HttpContext.Session.SetInt32("LastPizzaOrderId", order.Id);
|
||||||
|
|
||||||
TempData["Success"] = $"Tack {order.CustomerName}! Din pizza <EFBFBD>r best<EFBFBD>lld!";
|
TempData["Success"] = $"Tack {order.CustomerName}! Din pizza är beställd!";
|
||||||
return RedirectToAction("PizzaOrder");
|
return RedirectToAction("PizzaOrder");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +159,14 @@ namespace Aberwyn.Controllers
|
|||||||
|
|
||||||
var allMeals = _menuService.GetMeals();
|
var allMeals = _menuService.GetMeals();
|
||||||
|
|
||||||
|
var categories = _menuService.GetMealCategories();
|
||||||
|
var pizzaCategory = categories.FirstOrDefault(c => c.Name.Equals("Pizza", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
viewModel.AvailablePizzas = allMeals
|
viewModel.AvailablePizzas = allMeals
|
||||||
.Where(m => m.Category == "Pizza")
|
.Where(m => m.MealCategoryId == pizzaCategory?.Id)
|
||||||
.OrderBy(m => m.Name)
|
.OrderBy(m => m.Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
ViewBag.RestaurantIsOpen = GetRestaurantStatus();
|
||||||
|
|
||||||
@@ -167,19 +178,42 @@ namespace Aberwyn.Controllers
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Roles = "Chef")]
|
[Authorize(Roles = "Chef")]
|
||||||
public IActionResult UpdatePizzaOrder(int id, string status, string ingredientsJson)
|
public async Task<IActionResult> UpdatePizzaOrder(int id, string status, string ingredientsJson)
|
||||||
{
|
{
|
||||||
var order = _context.PizzaOrders.FirstOrDefault(p => p.Id == id);
|
var order = await _context.PizzaOrders.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
if (order != null)
|
if (order == null)
|
||||||
|
return RedirectToAction("PizzaAdmin");
|
||||||
|
|
||||||
|
order.Status = status;
|
||||||
|
order.IngredientsJson = ingredientsJson;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Skicka pushnotiser till kopplade prenumeranter
|
||||||
|
var subscribers = await _context.PushSubscribers
|
||||||
|
.Where(s => s.PizzaOrderId == id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var payload = $@"{{
|
||||||
|
""title"": ""Din pizza! 🍕"",
|
||||||
|
""body"": ""Statusuppdatering: {status}""
|
||||||
|
}}";
|
||||||
|
|
||||||
|
foreach (var sub in subscribers)
|
||||||
{
|
{
|
||||||
order.Status = status;
|
try
|
||||||
order.IngredientsJson = ingredientsJson;
|
{
|
||||||
_context.SaveChanges();
|
_notificationService.SendNotification(sub.Endpoint, sub.P256DH, sub.Auth, payload);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"❌ Kunde inte skicka notis till {sub.Endpoint}: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return RedirectToAction("PizzaAdmin");
|
return RedirectToAction("PizzaAdmin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Authorize(Roles = "Chef")]
|
[Authorize(Roles = "Chef")]
|
||||||
public IActionResult Veckomeny(int? week, int? year)
|
public IActionResult Veckomeny(int? week, int? year)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,14 @@ namespace Aberwyn.Controllers
|
|||||||
{
|
{
|
||||||
var isOpen = _context.AppSettings.FirstOrDefault(x => x.Key == "RestaurantIsOpen")?.Value == "True";
|
var isOpen = _context.AppSettings.FirstOrDefault(x => x.Key == "RestaurantIsOpen")?.Value == "True";
|
||||||
ViewBag.RestaurantIsOpen = isOpen;
|
ViewBag.RestaurantIsOpen = isOpen;
|
||||||
return View();
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var showDate = now.Hour >= 20 ? now.Date.AddDays(1) : now.Date;
|
||||||
|
|
||||||
|
var todaysMenu = _menuService.GetMenuForDate(showDate);
|
||||||
|
|
||||||
|
ViewBag.ShowDate = showDate;
|
||||||
|
return View(todaysMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Privacy()
|
public IActionResult Privacy()
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ namespace Aberwyn.Controllers
|
|||||||
CreatedAt = DateTime.Now
|
CreatedAt = DateTime.Now
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
ViewBag.Categories = _menuService.GetMealCategories()
|
||||||
|
.Where(c => c.IsActive)
|
||||||
|
.OrderBy(c => c.Name)
|
||||||
|
.ToList();
|
||||||
ViewData["IsEditing"] = edit;
|
ViewData["IsEditing"] = edit;
|
||||||
return View("View", meal);
|
return View("View", meal);
|
||||||
}
|
}
|
||||||
@@ -164,5 +167,123 @@ namespace Aberwyn.Controllers
|
|||||||
//service.DeleteMeal(id);
|
//service.DeleteMeal(id);
|
||||||
return RedirectToAction("Edit"); // eller tillbaka till lista
|
return RedirectToAction("Edit"); // eller tillbaka till lista
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "Admin,Chef")]
|
||||||
|
[HttpGet("/meal/categories")]
|
||||||
|
public IActionResult Categories()
|
||||||
|
{
|
||||||
|
var categories = _menuService.GetMealCategories()
|
||||||
|
.Select(cat => {
|
||||||
|
cat.MealCount = _menuService.GetMealCountForCategory(cat.Id);
|
||||||
|
return cat;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return View("MealCategories", categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "Admin,Chef")]
|
||||||
|
[HttpPost("/meal/categories/save")]
|
||||||
|
public IActionResult SaveCategory(MealCategory category)
|
||||||
|
{
|
||||||
|
_menuService.SaveOrUpdateCategory(category);
|
||||||
|
return RedirectToAction("Categories");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Roles = "Admin,Chef")]
|
||||||
|
[HttpPost("/meal/categories/delete")]
|
||||||
|
public IActionResult DeleteCategory(int id)
|
||||||
|
{
|
||||||
|
_menuService.DeleteCategory(id);
|
||||||
|
return RedirectToAction("Categories");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("/meal/lab")]
|
||||||
|
public IActionResult Lab(int? id)
|
||||||
|
{
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
var entry = _menuService.GetRecipeLabEntryById(id.Value);
|
||||||
|
if (entry == null) return NotFound();
|
||||||
|
|
||||||
|
// Hämta versioner först när vi vet att entry finns
|
||||||
|
entry.Versions = _menuService.GetLabVersionsForEntry(id.Value);
|
||||||
|
return View("Lab", entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skapa ett tomt labb-entry för formulär
|
||||||
|
var newEntry = new RecipeLabEntry
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Inspiration = "",
|
||||||
|
Notes = "",
|
||||||
|
Tags = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
return View("Lab", newEntry);
|
||||||
}
|
}
|
||||||
|
// Lägg till dessa actions i din MealController
|
||||||
|
|
||||||
|
[HttpPost("/meal/lab/save")]
|
||||||
|
public IActionResult SaveLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
if (entry.Id == 0)
|
||||||
|
{
|
||||||
|
// Ny entry
|
||||||
|
entry.CreatedAt = DateTime.Now;
|
||||||
|
_menuService.AddLabEntry(entry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Uppdatera befintlig
|
||||||
|
_menuService.UpdateLabEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction("Lab", new { id = entry.Id });
|
||||||
|
}
|
||||||
|
[HttpPost("/meal/lab/create")]
|
||||||
|
public IActionResult CreateLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
entry.CreatedAt = DateTime.Now;
|
||||||
|
_menuService.AddLabEntry(entry);
|
||||||
|
|
||||||
|
return RedirectToAction("Lab", new { id = entry.Id });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/meal/lab/save-ingredients")]
|
||||||
|
public IActionResult SaveLabIngredients(int RecipeLabEntryId, List<LabIngredient> Ingredients)
|
||||||
|
{
|
||||||
|
_menuService.SaveIngredientsForLabEntry(RecipeLabEntryId, Ingredients);
|
||||||
|
return RedirectToAction("Lab", new { id = RecipeLabEntryId });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("/meal/lab/addversion")]
|
||||||
|
public IActionResult AddLabVersion(RecipeLabVersion version)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
|
||||||
|
|
||||||
|
var entry = _menuService.GetRecipeLabEntryById(version.RecipeLabEntryId);
|
||||||
|
if (entry == null) return NotFound();
|
||||||
|
|
||||||
|
version.CreatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
// Kopiera nuvarande ingredienser
|
||||||
|
var copiedIngredients = entry.Ingredients
|
||||||
|
.Select(i => new LabVersionIngredient
|
||||||
|
{
|
||||||
|
Quantity = i.Quantity,
|
||||||
|
Item = i.Item
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_menuService.SaveLabVersionWithIngredients(version, copiedIngredients);
|
||||||
|
|
||||||
|
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,24 @@ namespace Aberwyn.Controllers
|
|||||||
return Ok(menu ?? new List<WeeklyMenu>());
|
return Ok(menu ?? new List<WeeklyMenu>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("getPublishedMeals")]
|
||||||
|
public IActionResult GetPublishedMeals([FromQuery] bool includeUnpublished = false)
|
||||||
|
{
|
||||||
|
var meals = _menuService.GetMeals()
|
||||||
|
.Where(m => includeUnpublished || m.IsPublished)
|
||||||
|
.Select(m => new {
|
||||||
|
m.Id,
|
||||||
|
m.Name,
|
||||||
|
m.Description,
|
||||||
|
ThumbnailData = m.ThumbnailData != null ? Convert.ToBase64String(m.ThumbnailData) : null
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Ok(meals);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("getMeals")]
|
[HttpGet("getMeals")]
|
||||||
public IActionResult GetMeals()
|
public IActionResult GetMeals()
|
||||||
{
|
{
|
||||||
@@ -45,6 +63,23 @@ namespace Aberwyn.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("today")]
|
||||||
|
public IActionResult GetTodayMenu()
|
||||||
|
{
|
||||||
|
var today = DateTime.Today;
|
||||||
|
var menu = _menuService.GetMenuForDate(today);
|
||||||
|
|
||||||
|
if (menu == null)
|
||||||
|
return NotFound(new { message = "Ingen meny hittades för idag." });
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
date = today.ToString("yyyy-MM-dd"),
|
||||||
|
lunch = menu.LunchMealName ?? "",
|
||||||
|
dinner = menu.DinnerMealName ?? "",
|
||||||
|
breakfast = menu.BreakfastMealName ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPut("menu")]
|
[HttpPut("menu")]
|
||||||
|
|||||||
79
Aberwyn/Controllers/MealRatingApiController.cs
Normal file
79
Aberwyn/Controllers/MealRatingApiController.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Aberwyn.Data;
|
||||||
|
using Aberwyn.Models;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Aberwyn.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class MealRatingApiController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
|
public MealRatingApiController(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{mealId}")]
|
||||||
|
public async Task<IActionResult> GetRating(int mealId)
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var rating = await _context.MealRatings
|
||||||
|
.FirstOrDefaultAsync(r => r.MealId == mealId && r.UserId == user.Id);
|
||||||
|
|
||||||
|
return Ok(rating?.Rating ?? 0);
|
||||||
|
}
|
||||||
|
[HttpGet("average/{mealId}")]
|
||||||
|
public async Task<IActionResult> GetAverageRating(int mealId)
|
||||||
|
{
|
||||||
|
var ratings = await _context.MealRatings
|
||||||
|
.Where(r => r.MealId == mealId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (ratings.Count == 0)
|
||||||
|
return Ok(0);
|
||||||
|
|
||||||
|
var avg = ratings.Average(r => r.Rating);
|
||||||
|
return Ok(avg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> SetRating([FromBody] MealRatingDto model)
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existing = await _context.MealRatings
|
||||||
|
.FirstOrDefaultAsync(r => r.MealId == model.MealId && r.UserId == user.Id);
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Rating = model.Rating;
|
||||||
|
existing.CreatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_context.MealRatings.Add(new MealRating
|
||||||
|
{
|
||||||
|
MealId = model.MealId,
|
||||||
|
UserId = user.Id,
|
||||||
|
Rating = model.Rating,
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
Aberwyn/Controllers/MovieController.cs
Normal file
13
Aberwyn/Controllers/MovieController.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Aberwyn.Controllers
|
||||||
|
{
|
||||||
|
public class MovieController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("/movie/search")]
|
||||||
|
public IActionResult Search()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,37 +38,72 @@ namespace Aberwyn.Controllers
|
|||||||
return Ok(new { message = $"Skickade pizzanotiser till {count} användare." });
|
return Ok(new { message = $"Skickade pizzanotiser till {count} användare." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("subscribe-user")]
|
||||||
[HttpPost("subscribe")]
|
public async Task<IActionResult> SubscribeUser([FromBody] PushSubscription subscription)
|
||||||
public async Task<IActionResult> Subscribe([FromBody] PushSubscription subscription)
|
|
||||||
{
|
{
|
||||||
var existing = await _context.PushSubscribers
|
|
||||||
.FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint);
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
var user = await _userManager.GetUserAsync(User);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existing = await _context.PushSubscribers
|
||||||
|
.FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint);
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
var newSubscriber = new PushSubscriber
|
var newSub = new PushSubscriber
|
||||||
{
|
{
|
||||||
Endpoint = subscription.Endpoint,
|
Endpoint = subscription.Endpoint,
|
||||||
P256DH = subscription.Keys["p256dh"],
|
P256DH = subscription.Keys["p256dh"],
|
||||||
Auth = subscription.Keys["auth"],
|
Auth = subscription.Keys["auth"],
|
||||||
UserId = user.Id
|
UserId = user.Id
|
||||||
};
|
};
|
||||||
|
_context.PushSubscribers.Add(newSub);
|
||||||
_context.PushSubscribers.Add(newSubscriber);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
existing.P256DH = subscription.Keys["p256dh"];
|
existing.P256DH = subscription.Keys["p256dh"];
|
||||||
existing.Auth = subscription.Keys["auth"];
|
existing.Auth = subscription.Keys["auth"];
|
||||||
|
existing.UserId = user.Id;
|
||||||
}
|
}
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("subscribe")]
|
||||||
|
public async Task<IActionResult> Subscribe([FromBody] PushSubscriptionWithOrder dto)
|
||||||
|
{
|
||||||
|
var existing = await _context.PushSubscribers
|
||||||
|
.FirstOrDefaultAsync(s => s.Endpoint == dto.Subscription.Endpoint);
|
||||||
|
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
var newSub = new PushSubscriber
|
||||||
|
{
|
||||||
|
Endpoint = dto.Subscription.Endpoint,
|
||||||
|
P256DH = dto.Subscription.Keys["p256dh"],
|
||||||
|
Auth = dto.Subscription.Keys["auth"],
|
||||||
|
PizzaOrderId = dto.PizzaOrderId
|
||||||
|
};
|
||||||
|
_context.PushSubscribers.Add(newSub);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.P256DH = dto.Subscription.Keys["p256dh"];
|
||||||
|
existing.Auth = dto.Subscription.Keys["auth"];
|
||||||
|
existing.PizzaOrderId = dto.PizzaOrderId; // uppdatera kopplingen
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushSubscriptionWithOrder
|
||||||
|
{
|
||||||
|
public PushSubscription Subscription { get; set; }
|
||||||
|
public int PizzaOrderId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("vapid-public-key")]
|
[HttpGet("vapid-public-key")]
|
||||||
public IActionResult GetVapidKey([FromServices] IConfiguration config)
|
public IActionResult GetVapidKey([FromServices] IConfiguration config)
|
||||||
{
|
{
|
||||||
@@ -109,6 +144,24 @@ namespace Aberwyn.Controllers
|
|||||||
return Ok($"Skickade notiser till {successCount} användare.");
|
return Ok($"Skickade notiser till {successCount} användare.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("unsubscribe")]
|
||||||
|
public async Task<IActionResult> Unsubscribe([FromBody] PushUnsubscribeDto dto)
|
||||||
|
{
|
||||||
|
var sub = await _context.PushSubscribers.FirstOrDefaultAsync(s => s.Endpoint == dto.Endpoint);
|
||||||
|
if (sub != null)
|
||||||
|
{
|
||||||
|
_context.PushSubscribers.Remove(sub);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushUnsubscribeDto
|
||||||
|
{
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,43 +22,5 @@ namespace Aberwyn.Controllers
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<IActionResult> GetReport([FromBody] BudgetReportRequestDto request)
|
|
||||||
{
|
|
||||||
var start = new DateTime(request.StartYear, request.StartMonth, 1);
|
|
||||||
var end = new DateTime(request.EndYear, request.EndMonth, 1);
|
|
||||||
|
|
||||||
var items = await _context.BudgetItems
|
|
||||||
.Include(i => i.BudgetItemDefinition)
|
|
||||||
.Include(i => i.BudgetCategory)
|
|
||||||
.ThenInclude(c => c.BudgetPeriod)
|
|
||||||
.Where(i =>
|
|
||||||
i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month >= start.Year * 12 + start.Month &&
|
|
||||||
i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month <= end.Year * 12 + end.Month &&
|
|
||||||
request.DefinitionIds.Contains(i.BudgetItemDefinitionId ?? -1))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var grouped = items
|
|
||||||
.GroupBy(i => new { i.BudgetCategory.BudgetPeriod.Year, i.BudgetCategory.BudgetPeriod.Month })
|
|
||||||
.Select(g => new BudgetReportResultDto
|
|
||||||
{
|
|
||||||
Year = g.Key.Year,
|
|
||||||
Month = g.Key.Month,
|
|
||||||
Definitions = g
|
|
||||||
.GroupBy(i => new { i.BudgetItemDefinitionId, i.BudgetItemDefinition.Name })
|
|
||||||
.Select(dg => new DefinitionSumDto
|
|
||||||
{
|
|
||||||
DefinitionId = dg.Key.BudgetItemDefinitionId ?? 0,
|
|
||||||
DefinitionName = dg.Key.Name,
|
|
||||||
TotalAmount = dg.Sum(x => x.Amount)
|
|
||||||
}).ToList()
|
|
||||||
})
|
|
||||||
.OrderBy(r => r.Year).ThenBy(r => r.Month)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return Ok(grouped);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,22 @@ namespace Aberwyn.Data
|
|||||||
base.OnModelCreating(builder);
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
builder.Entity<WeeklyMenu>().ToTable("WeeklyMenu");
|
builder.Entity<WeeklyMenu>().ToTable("WeeklyMenu");
|
||||||
|
builder.Entity<MealCategory>().HasData(
|
||||||
|
new MealCategory
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza",
|
||||||
|
Icon = "🍕",
|
||||||
|
Color = "#f97316",
|
||||||
|
IsActive = true,
|
||||||
|
DisplayOrder = 1
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public DbSet<BudgetPeriod> BudgetPeriods { get; set; }
|
public DbSet<BudgetPeriod> BudgetPeriods { get; set; }
|
||||||
public DbSet<BudgetCategory> BudgetCategories { get; set; }
|
public DbSet<BudgetCategory> BudgetCategories { get; set; }
|
||||||
public DbSet<BudgetItem> BudgetItems { get; set; }
|
public DbSet<BudgetItem> BudgetItems { get; set; }
|
||||||
@@ -33,6 +46,13 @@ namespace Aberwyn.Data
|
|||||||
public DbSet<Ingredient> Ingredients { get; set; }
|
public DbSet<Ingredient> Ingredients { get; set; }
|
||||||
public DbSet<UserPreferences> UserPreferences { get; set; }
|
public DbSet<UserPreferences> UserPreferences { get; set; }
|
||||||
public DbSet<StoredPushSubscription> PushSubscriptions { get; set; }
|
public DbSet<StoredPushSubscription> PushSubscriptions { get; set; }
|
||||||
|
public DbSet<MealCategory> MealCategories { get; set; }
|
||||||
|
public DbSet<RecipeLabEntry> RecipeLabEntries { get; set; }
|
||||||
|
public DbSet<RecipeLabVersion> RecipeLabVersions { get; set; }
|
||||||
|
public DbSet<LabIngredient> LabIngredients { get; set; }
|
||||||
|
public DbSet<LabVersionIngredient> LabVersionIngredients { get; set; }
|
||||||
|
public DbSet<MealRating> MealRatings { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ public List<Meal> GetMealsSlim(bool includeThumbnail = false)
|
|||||||
public List<Meal> GetMealsByCategory(string category)
|
public List<Meal> GetMealsByCategory(string category)
|
||||||
{
|
{
|
||||||
return _context.Meals
|
return _context.Meals
|
||||||
.Where(m => m.Category == category)
|
//.Where(m => m.Category == category)
|
||||||
.Include(m => m.Ingredients)
|
.Include(m => m.Ingredients)
|
||||||
.OrderBy(m => m.Name)
|
.OrderBy(m => m.Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -339,6 +339,71 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
|||||||
|
|
||||||
return menus;
|
return menus;
|
||||||
}
|
}
|
||||||
|
public WeeklyMenu? GetMenuForDate(DateTime date)
|
||||||
|
{
|
||||||
|
int week = ISOWeek.GetWeekOfYear(date);
|
||||||
|
int year = date.Year;
|
||||||
|
|
||||||
|
int dayOfWeek = (int)date.DayOfWeek;
|
||||||
|
if (dayOfWeek == 0) dayOfWeek = 7;
|
||||||
|
|
||||||
|
var menu = _context.WeeklyMenus
|
||||||
|
.FirstOrDefault(w => w.WeekNumber == week && w.Year == year && w.DayOfWeek == dayOfWeek);
|
||||||
|
|
||||||
|
if (menu != null)
|
||||||
|
{
|
||||||
|
var mealIds = new[] { menu.BreakfastMealId, menu.LunchMealId, menu.DinnerMealId }
|
||||||
|
.Where(id => id.HasValue)
|
||||||
|
.Select(id => id.Value)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var allMeals = _context.Meals
|
||||||
|
.Where(m => mealIds.Contains(m.Id))
|
||||||
|
.ToDictionary(m => m.Id);
|
||||||
|
|
||||||
|
if (menu.BreakfastMealId is int bId && allMeals.TryGetValue(bId, out var breakfast))
|
||||||
|
{
|
||||||
|
menu.BreakfastMealName = breakfast.Name;
|
||||||
|
menu.BreakfastThumbnail = breakfast.ThumbnailData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.LunchMealId is int lId && allMeals.TryGetValue(lId, out var lunch))
|
||||||
|
{
|
||||||
|
menu.LunchMealName = lunch.Name;
|
||||||
|
menu.LunchThumbnail = lunch.ThumbnailData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.DinnerMealId is int dId && allMeals.TryGetValue(dId, out var dinner))
|
||||||
|
{
|
||||||
|
menu.DinnerMealName = dinner.Name;
|
||||||
|
menu.DinnerThumbnail = dinner.ThumbnailData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
public List<Meal> GetMealsByCategoryName(string categoryName, string? searchTerm = null, bool onlyAvailable = false)
|
||||||
|
{
|
||||||
|
var query = _context.Meals
|
||||||
|
.Include(m => m.Category)
|
||||||
|
.Include(m => m.Ingredients)
|
||||||
|
.Where(m => m.Category != null && m.Category.Name == categoryName);
|
||||||
|
|
||||||
|
if (onlyAvailable)
|
||||||
|
query = query.Where(m => m.IsAvailable);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||||
|
{
|
||||||
|
string lowered = searchTerm.Trim().ToLower();
|
||||||
|
query = query.Where(m => m.Name.ToLower().Contains(lowered));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
.OrderBy(m => m.Name)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
@@ -370,5 +435,126 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
public List<MealCategory> GetMealCategories()
|
||||||
|
{
|
||||||
|
return _context.MealCategories.OrderBy(c => c.DisplayOrder).ToList();
|
||||||
|
}
|
||||||
|
public int GetMealCountForCategory(int categoryId)
|
||||||
|
{
|
||||||
|
return _context.Meals.Count(m => m.MealCategoryId == categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveOrUpdateCategory(MealCategory cat)
|
||||||
|
{
|
||||||
|
if (cat.Id == 0)
|
||||||
|
{
|
||||||
|
_context.MealCategories.Add(cat);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var existing = _context.MealCategories.Find(cat.Id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Name = cat.Name;
|
||||||
|
existing.Slug = cat.Slug;
|
||||||
|
existing.Description = cat.Description;
|
||||||
|
existing.Icon = cat.Icon;
|
||||||
|
existing.Color = cat.Color;
|
||||||
|
existing.IsActive = cat.IsActive;
|
||||||
|
existing.DisplayOrder = cat.DisplayOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
#region Lab
|
||||||
|
public void DeleteCategory(int id)
|
||||||
|
{
|
||||||
|
var cat = _context.MealCategories.Find(id);
|
||||||
|
if (cat != null)
|
||||||
|
{
|
||||||
|
_context.MealCategories.Remove(cat);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public RecipeLabEntry? GetRecipeLabEntryById(int id)
|
||||||
|
{
|
||||||
|
return _context.RecipeLabEntries
|
||||||
|
.Include(e => e.Versions)
|
||||||
|
.FirstOrDefault(e => e.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddVersionToLabEntry(RecipeLabVersion version)
|
||||||
|
{
|
||||||
|
_context.RecipeLabVersions.Add(version);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
_context.RecipeLabEntries.Add(entry);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
public void SaveLabVersionWithIngredients(RecipeLabVersion version, List<LabVersionIngredient> ingredients)
|
||||||
|
{
|
||||||
|
_context.RecipeLabVersions.Add(version);
|
||||||
|
_context.SaveChanges(); // så vi får ett ID
|
||||||
|
|
||||||
|
foreach (var ing in ingredients)
|
||||||
|
{
|
||||||
|
ing.RecipeLabVersionId = version.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.LabVersionIngredients.AddRange(ingredients);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
// Lägg till dessa metoder i din MenuService klass
|
||||||
|
|
||||||
|
public void UpdateLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
var existing = _context.RecipeLabEntries.Find(entry.Id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Title = entry.Title;
|
||||||
|
existing.Inspiration = entry.Inspiration;
|
||||||
|
existing.Notes = entry.Notes;
|
||||||
|
existing.Tags = entry.Tags;
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecipeLabEntry GetRecipeLabEntryWithIngredients(int id)
|
||||||
|
{
|
||||||
|
return _context.RecipeLabEntries
|
||||||
|
.Include(e => e.Versions)
|
||||||
|
.Include(e => e.Ingredients)
|
||||||
|
.FirstOrDefault(e => e.Id == id);
|
||||||
|
}
|
||||||
|
public void SaveIngredientsForLabEntry(int labEntryId, List<LabIngredient> ingredients)
|
||||||
|
{
|
||||||
|
var existing = _context.LabIngredients
|
||||||
|
.Where(i => i.RecipeLabEntryId == labEntryId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_context.LabIngredients.RemoveRange(existing);
|
||||||
|
|
||||||
|
foreach (var ing in ingredients)
|
||||||
|
ing.RecipeLabEntryId = labEntryId;
|
||||||
|
|
||||||
|
_context.LabIngredients.AddRange(ingredients);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RecipeLabVersion> GetLabVersionsForEntry(int entryId)
|
||||||
|
{
|
||||||
|
return _context.RecipeLabVersions
|
||||||
|
.Where(v => v.RecipeLabEntryId == entryId)
|
||||||
|
.Include(v => v.Ingredients)
|
||||||
|
.OrderByDescending(v => v.CreatedAt)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,15 @@ namespace Aberwyn.Services
|
|||||||
s.Endpoint.StartsWith("https://"))
|
s.Endpoint.StartsWith("https://"))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var allSubscribers = await _context.PushSubscribers
|
||||||
int successCount = 0;
|
.Include(s => s.User)
|
||||||
|
.ThenInclude(u => u.Preferences)
|
||||||
|
.ToListAsync();
|
||||||
|
foreach (var s in allSubscribers)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"🔍 Sub: {s.Endpoint}, User: {s.User?.UserName}, NotifyPizza: {s.User?.Preferences?.NotifyPizza}");
|
||||||
|
}
|
||||||
|
int successCount = 0;
|
||||||
foreach (var sub in subscribers)
|
foreach (var sub in subscribers)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
# Basimage för runtime
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
|
|
||||||
|
# Installera svenska språkinställningar
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y locales && \
|
apt-get install -y locales && \
|
||||||
locale-gen sv_SE.UTF-8
|
locale-gen sv_SE.UTF-8
|
||||||
|
|
||||||
|
# Ställ in svenska som standard
|
||||||
ENV LANG=sv_SE.UTF-8
|
ENV LANG=sv_SE.UTF-8
|
||||||
ENV LANGUAGE=sv_SE:sv
|
ENV LANGUAGE=sv_SE:sv
|
||||||
ENV LC_ALL=sv_SE.UTF-8
|
ENV LC_ALL=sv_SE.UTF-8
|
||||||
@@ -13,18 +16,30 @@ WORKDIR /app
|
|||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 443
|
EXPOSE 443
|
||||||
|
|
||||||
|
# Byggimage med SDK
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Kopiera endast .csproj först för att kunna cacha restore
|
||||||
COPY ["Aberwyn/Aberwyn.csproj", "Aberwyn/"]
|
COPY ["Aberwyn/Aberwyn.csproj", "Aberwyn/"]
|
||||||
WORKDIR /src/Aberwyn
|
WORKDIR /src/Aberwyn
|
||||||
|
|
||||||
|
# Restore beroenden
|
||||||
RUN dotnet restore "Aberwyn.csproj"
|
RUN dotnet restore "Aberwyn.csproj"
|
||||||
|
|
||||||
|
# Kopiera övrig kod
|
||||||
COPY Aberwyn/. .
|
COPY Aberwyn/. .
|
||||||
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build
|
|
||||||
|
|
||||||
|
# Bygg utan att köra restore igen
|
||||||
|
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build --no-restore
|
||||||
|
|
||||||
|
# Publicera utan att köra restore eller build igen
|
||||||
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 --no-restore
|
||||||
|
|
||||||
|
# Slutgiltig image baserad på runtime
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
|
|
||||||
ENTRYPOINT ["dotnet", "Aberwyn.dll"]
|
ENTRYPOINT ["dotnet", "Aberwyn.dll"]
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Aberwyn.Migrations
|
|
||||||
{
|
|
||||||
public partial class AssignedToNullable : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "AssignedTo",
|
|
||||||
table: "TodoTasks",
|
|
||||||
type: "longtext",
|
|
||||||
nullable: true,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "longtext")
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4")
|
|
||||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "TodoTasks",
|
|
||||||
keyColumn: "AssignedTo",
|
|
||||||
keyValue: null,
|
|
||||||
column: "AssignedTo",
|
|
||||||
value: "");
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "AssignedTo",
|
|
||||||
table: "TodoTasks",
|
|
||||||
type: "longtext",
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "longtext",
|
|
||||||
oldNullable: true)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4")
|
|
||||||
.OldAnnotation("MySql:CharSet", "utf8mb4");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Aberwyn.Migrations
|
|
||||||
{
|
|
||||||
public partial class AddUserPreferencesAndPushSubscriptions : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "PushSubscriptions",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(type: "int", nullable: false)
|
|
||||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
|
||||||
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
|
||||||
Endpoint = table.Column<string>(type: "longtext", nullable: false)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
|
||||||
P256DH = table.Column<string>(type: "longtext", nullable: false)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
|
||||||
Auth = table.Column<string>(type: "longtext", nullable: false)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4")
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_PushSubscriptions", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_PushSubscriptions_AspNetUsers_UserId",
|
|
||||||
column: x => x.UserId,
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
})
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserPreferences",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
|
||||||
NotifyPizza = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
|
||||||
NotifyMenu = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
|
||||||
NotifyBudget = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserPreferences", x => x.UserId);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserPreferences_AspNetUsers_UserId",
|
|
||||||
column: x => x.UserId,
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
})
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_PushSubscriptions_UserId",
|
|
||||||
table: "PushSubscriptions",
|
|
||||||
column: "UserId");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "PushSubscriptions");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "UserPreferences");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Aberwyn.Migrations
|
|
||||||
{
|
|
||||||
public partial class AddPushSubscriberUserLink : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "UserId",
|
|
||||||
table: "PushSubscribers",
|
|
||||||
type: "varchar(255)",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "")
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_PushSubscribers_UserId",
|
|
||||||
table: "PushSubscribers",
|
|
||||||
column: "UserId");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_PushSubscribers_AspNetUsers_UserId",
|
|
||||||
table: "PushSubscribers",
|
|
||||||
column: "UserId",
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_PushSubscribers_AspNetUsers_UserId",
|
|
||||||
table: "PushSubscribers");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_PushSubscribers_UserId",
|
|
||||||
table: "PushSubscribers");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "UserId",
|
|
||||||
table: "PushSubscribers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Aberwyn.Migrations
|
namespace Aberwyn.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20250606100439_AddPushSubscriberUserLink")]
|
[Migration("20250618203117_AddPaymentStatusToBudgetItem")]
|
||||||
partial class AddPushSubscriberUserLink
|
partial class AddPaymentStatusToBudgetItem
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BudgetCategoryId");
|
b.HasIndex("BudgetCategoryId");
|
||||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("DefaultCategory")
|
b.Property<string>("DefaultCategory")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("DefaultPaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IncludeInSummary")
|
b.Property<bool>("IncludeInSummary")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("CarbType")
|
b.Property<string>("CarbType")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -293,6 +296,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<bool>("IsAvailable")
|
b.Property<bool>("IsAvailable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int?>("MealCategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -308,9 +314,56 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealCategoryId");
|
||||||
|
|
||||||
b.ToTable("Meals");
|
b.ToTable("Meals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MealCategories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Color = "#f97316",
|
||||||
|
DisplayOrder = 1,
|
||||||
|
Icon = "🍕",
|
||||||
|
IsActive = true,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -358,12 +411,17 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PizzaOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("UserId")
|
b.Property<string>("UserId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PizzaOrderId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("PushSubscribers");
|
b.ToTable("PushSubscribers");
|
||||||
@@ -638,7 +696,7 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||||
.WithMany("Items")
|
.WithMany("Items")
|
||||||
.HasForeignKey("BudgetCategoryId")
|
.HasForeignKey("BudgetCategoryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -648,8 +706,6 @@ namespace Aberwyn.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BudgetItemDefinitionId");
|
.HasForeignKey("BudgetItemDefinitionId");
|
||||||
|
|
||||||
b.Navigation("BudgetCategory");
|
|
||||||
|
|
||||||
b.Navigation("BudgetItemDefinition");
|
b.Navigation("BudgetItemDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -662,14 +718,29 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||||
|
.WithMany("Meals")
|
||||||
|
.HasForeignKey("MealCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||||
{
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PizzaOrderId");
|
||||||
|
|
||||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("PizzaOrder");
|
||||||
|
|
||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -766,6 +837,11 @@ namespace Aberwyn.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Ingredients");
|
b.Navigation("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Meals");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddPaymentStatusToBudgetItem : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "PaymentStatus",
|
||||||
|
table: "BudgetItems",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "DefaultPaymentStatus",
|
||||||
|
table: "BudgetItemDefinitions",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PaymentStatus",
|
||||||
|
table: "BudgetItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "DefaultPaymentStatus",
|
||||||
|
table: "BudgetItemDefinitions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Aberwyn.Migrations
|
namespace Aberwyn.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20250606061016_AddUserPreferencesAndPushSubscriptions")]
|
[Migration("20250624150426_AddIsPublishedToMeals")]
|
||||||
partial class AddUserPreferencesAndPushSubscriptions
|
partial class AddIsPublishedToMeals
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BudgetCategoryId");
|
b.HasIndex("BudgetCategoryId");
|
||||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("DefaultCategory")
|
b.Property<string>("DefaultCategory")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("DefaultPaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IncludeInSummary")
|
b.Property<bool>("IncludeInSummary")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("CarbType")
|
b.Property<string>("CarbType")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -293,6 +296,12 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<bool>("IsAvailable")
|
b.Property<bool>("IsAvailable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPublished")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int?>("MealCategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -308,9 +317,56 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealCategoryId");
|
||||||
|
|
||||||
b.ToTable("Meals");
|
b.ToTable("Meals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MealCategories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Color = "#f97316",
|
||||||
|
DisplayOrder = 1,
|
||||||
|
Icon = "🍕",
|
||||||
|
IsActive = true,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -358,8 +414,19 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PizzaOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("PushSubscribers");
|
b.ToTable("PushSubscribers");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -632,7 +699,7 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||||
.WithMany("Items")
|
.WithMany("Items")
|
||||||
.HasForeignKey("BudgetCategoryId")
|
.HasForeignKey("BudgetCategoryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -642,8 +709,6 @@ namespace Aberwyn.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BudgetItemDefinitionId");
|
.HasForeignKey("BudgetItemDefinitionId");
|
||||||
|
|
||||||
b.Navigation("BudgetCategory");
|
|
||||||
|
|
||||||
b.Navigation("BudgetItemDefinition");
|
b.Navigation("BudgetItemDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -656,6 +721,32 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||||
|
.WithMany("Meals")
|
||||||
|
.HasForeignKey("MealCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("PizzaOrder");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
@@ -749,6 +840,11 @@ namespace Aberwyn.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Ingredients");
|
b.Navigation("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Meals");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace Aberwyn.Migrations
|
namespace Aberwyn.Migrations
|
||||||
{
|
{
|
||||||
public partial class AddThumbnailToMeal : Migration
|
public partial class AddIsPublishedToMeals : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.AddColumn<byte[]>(
|
migrationBuilder.AddColumn<bool>(
|
||||||
name: "ThumbnailData",
|
name: "IsPublished",
|
||||||
table: "Meals",
|
table: "Meals",
|
||||||
type: "longblob",
|
type: "tinyint(1)",
|
||||||
nullable: true);
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.DropColumn(
|
||||||
name: "ThumbnailData",
|
name: "IsPublished",
|
||||||
table: "Meals");
|
table: "Meals");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Aberwyn.Migrations
|
namespace Aberwyn.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20250605062804_AddThumbnailToMeal")]
|
[Migration("20250630123620_AddRecipeLab")]
|
||||||
partial class AddThumbnailToMeal
|
partial class AddRecipeLab
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BudgetCategoryId");
|
b.HasIndex("BudgetCategoryId");
|
||||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("DefaultCategory")
|
b.Property<string>("DefaultCategory")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("DefaultPaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IncludeInSummary")
|
b.Property<bool>("IncludeInSummary")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -269,9 +275,6 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("CarbType")
|
b.Property<string>("CarbType")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -293,6 +296,12 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<bool>("IsAvailable")
|
b.Property<bool>("IsAvailable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPublished")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int?>("MealCategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -308,9 +317,56 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealCategoryId");
|
||||||
|
|
||||||
b.ToTable("Meals");
|
b.ToTable("Meals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MealCategories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Color = "#f97316",
|
||||||
|
DisplayOrder = 1,
|
||||||
|
Icon = "🍕",
|
||||||
|
IsActive = true,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -358,11 +414,124 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PizzaOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("PushSubscribers");
|
b.ToTable("PushSubscribers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("BaseMealId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Inspiration")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("Rating")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("TestedBy")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BaseMealId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Ingredients")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Instructions")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabEntryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ResultNotes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("VersionLabel")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabEntryId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabVersions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Endpoint")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("P256DH")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptions");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -402,6 +571,25 @@ namespace Aberwyn.Migrations
|
|||||||
b.ToTable("TodoTasks");
|
b.ToTable("TodoTasks");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyBudget")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyMenu")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyPizza")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.HasKey("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UserPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -584,7 +772,7 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||||
.WithMany("Items")
|
.WithMany("Items")
|
||||||
.HasForeignKey("BudgetCategoryId")
|
.HasForeignKey("BudgetCategoryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -594,8 +782,6 @@ namespace Aberwyn.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BudgetItemDefinitionId");
|
.HasForeignKey("BudgetItemDefinitionId");
|
||||||
|
|
||||||
b.Navigation("BudgetCategory");
|
|
||||||
|
|
||||||
b.Navigation("BudgetItemDefinition");
|
b.Navigation("BudgetItemDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -608,6 +794,74 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||||
|
.WithMany("Meals")
|
||||||
|
.HasForeignKey("MealCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("PizzaOrder");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BaseMealId");
|
||||||
|
|
||||||
|
b.Navigation("BaseMeal");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||||
|
.WithMany("Versions")
|
||||||
|
.HasForeignKey("RecipeLabEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Entry");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithOne("Preferences")
|
||||||
|
.HasForeignKey("Aberwyn.Models.UserPreferences", "UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
@@ -659,6 +913,12 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Preferences")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
@@ -673,6 +933,16 @@ namespace Aberwyn.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Ingredients");
|
b.Navigation("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Meals");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Versions");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
95
Aberwyn/Migrations/20250630123620_AddRecipeLab.cs
Normal file
95
Aberwyn/Migrations/20250630123620_AddRecipeLab.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddRecipeLab : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RecipeLabEntries",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Title = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Category = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Inspiration = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
BaseMealId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Rating = table.Column<int>(type: "int", nullable: true),
|
||||||
|
TestedBy = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Tags = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RecipeLabEntries", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_RecipeLabEntries_Meals_BaseMealId",
|
||||||
|
column: x => x.BaseMealId,
|
||||||
|
principalTable: "Meals",
|
||||||
|
principalColumn: "Id");
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RecipeLabVersions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
VersionLabel = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Ingredients = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Instructions = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
ResultNotes = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RecipeLabVersions", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_RecipeLabVersions_RecipeLabEntries_RecipeLabEntryId",
|
||||||
|
column: x => x.RecipeLabEntryId,
|
||||||
|
principalTable: "RecipeLabEntries",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_RecipeLabEntries_BaseMealId",
|
||||||
|
table: "RecipeLabEntries",
|
||||||
|
column: "BaseMealId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_RecipeLabVersions_RecipeLabEntryId",
|
||||||
|
table: "RecipeLabVersions",
|
||||||
|
column: "RecipeLabEntryId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RecipeLabVersions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RecipeLabEntries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1026
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.Designer.cs
generated
Normal file
1026
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
80
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.cs
Normal file
80
Aberwyn/Migrations/20250630133813_AddLabIngredientModel.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddLabIngredientModel : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LabIngredients",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Quantity = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Item = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LabIngredients", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LabIngredients_RecipeLabEntries_RecipeLabEntryId",
|
||||||
|
column: x => x.RecipeLabEntryId,
|
||||||
|
principalTable: "RecipeLabEntries",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LabVersionIngredients",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
RecipeLabVersionId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Quantity = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Item = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LabVersionIngredients", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LabVersionIngredients_RecipeLabVersions_RecipeLabVersionId",
|
||||||
|
column: x => x.RecipeLabVersionId,
|
||||||
|
principalTable: "RecipeLabVersions",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LabIngredients_RecipeLabEntryId",
|
||||||
|
table: "LabIngredients",
|
||||||
|
column: "RecipeLabEntryId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LabVersionIngredients_RecipeLabVersionId",
|
||||||
|
table: "LabVersionIngredients",
|
||||||
|
column: "RecipeLabVersionId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LabIngredients");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LabVersionIngredients");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Aberwyn.Migrations
|
namespace Aberwyn.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20250604220420_AssignedToNullable")]
|
[Migration("20250630141059_AddRecipeLabModels")]
|
||||||
partial class AssignedToNullable
|
partial class AddRecipeLabModels
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -183,6 +183,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BudgetCategoryId");
|
b.HasIndex("BudgetCategoryId");
|
||||||
@@ -201,6 +204,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("DefaultCategory")
|
b.Property<string>("DefaultCategory")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("DefaultPaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IncludeInSummary")
|
b.Property<bool>("IncludeInSummary")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -260,6 +266,54 @@ namespace Aberwyn.Migrations
|
|||||||
b.ToTable("Ingredients");
|
b.ToTable("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Item")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Quantity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabEntryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabEntryId");
|
||||||
|
|
||||||
|
b.ToTable("LabIngredients");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Item")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Quantity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabVersionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabVersionId");
|
||||||
|
|
||||||
|
b.ToTable("LabVersionIngredients");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -269,9 +323,6 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("CarbType")
|
b.Property<string>("CarbType")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -293,6 +344,12 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<bool>("IsAvailable")
|
b.Property<bool>("IsAvailable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPublished")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int?>("MealCategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -303,11 +360,61 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("RecipeUrl")
|
b.Property<string>("RecipeUrl")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<byte[]>("ThumbnailData")
|
||||||
|
.HasColumnType("longblob");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealCategoryId");
|
||||||
|
|
||||||
b.ToTable("Meals");
|
b.ToTable("Meals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MealCategories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Color = "#f97316",
|
||||||
|
DisplayOrder = 1,
|
||||||
|
Icon = "🍕",
|
||||||
|
IsActive = true,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -355,11 +462,121 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PizzaOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("PushSubscribers");
|
b.ToTable("PushSubscribers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("BaseMealId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Inspiration")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("Rating")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("TestedBy")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BaseMealId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Instructions")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabEntryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ResultNotes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("VersionLabel")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabEntryId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabVersions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Endpoint")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("P256DH")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("PushSubscriptions");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -399,6 +616,25 @@ namespace Aberwyn.Migrations
|
|||||||
b.ToTable("TodoTasks");
|
b.ToTable("TodoTasks");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyBudget")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyMenu")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("NotifyPizza")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.HasKey("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UserPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -581,7 +817,7 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||||
.WithMany("Items")
|
.WithMany("Items")
|
||||||
.HasForeignKey("BudgetCategoryId")
|
.HasForeignKey("BudgetCategoryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -591,8 +827,6 @@ namespace Aberwyn.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BudgetItemDefinitionId");
|
.HasForeignKey("BudgetItemDefinitionId");
|
||||||
|
|
||||||
b.Navigation("BudgetCategory");
|
|
||||||
|
|
||||||
b.Navigation("BudgetItemDefinition");
|
b.Navigation("BudgetItemDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -605,6 +839,96 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||||
|
.WithMany("Ingredients")
|
||||||
|
.HasForeignKey("RecipeLabEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Entry");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabVersion", "Version")
|
||||||
|
.WithMany("Ingredients")
|
||||||
|
.HasForeignKey("RecipeLabVersionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Version");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||||
|
.WithMany("Meals")
|
||||||
|
.HasForeignKey("MealCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PizzaOrderId");
|
||||||
|
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("PizzaOrder");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BaseMealId");
|
||||||
|
|
||||||
|
b.Navigation("BaseMeal");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||||
|
.WithMany("Versions")
|
||||||
|
.HasForeignKey("RecipeLabEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Entry");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
|
.WithOne("Preferences")
|
||||||
|
.HasForeignKey("Aberwyn.Models.UserPreferences", "UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
@@ -656,6 +980,12 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Preferences")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
@@ -670,6 +1000,23 @@ namespace Aberwyn.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Ingredients");
|
b.Navigation("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Meals");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ingredients");
|
||||||
|
|
||||||
|
b.Navigation("Versions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ingredients");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
Aberwyn/Migrations/20250630141059_AddRecipeLabModels.cs
Normal file
26
Aberwyn/Migrations/20250630141059_AddRecipeLabModels.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddRecipeLabModels : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Ingredients",
|
||||||
|
table: "RecipeLabVersions");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Ingredients",
|
||||||
|
table: "RecipeLabVersions",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1060
Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs
generated
Normal file
1060
Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
Aberwyn/Migrations/20250707125951_AddMealRating.cs
Normal file
49
Aberwyn/Migrations/20250707125951_AddMealRating.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddMealRating : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "MealRatings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
MealId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
UserId = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Rating = table.Column<int>(type: "int", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_MealRatings", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_MealRatings_Meals_MealId",
|
||||||
|
column: x => x.MealId,
|
||||||
|
principalTable: "Meals",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MealRatings_MealId",
|
||||||
|
table: "MealRatings",
|
||||||
|
column: "MealId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "MealRatings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddNameToBudgetPeriod : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddNameToBudgetPeriod2 : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Name",
|
||||||
|
table: "BudgetPeriods",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Name",
|
||||||
|
table: "BudgetPeriods");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Aberwyn.Migrations
|
||||||
|
{
|
||||||
|
public partial class MakeYearMonthNullable : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Year",
|
||||||
|
table: "BudgetPeriods",
|
||||||
|
type: "int",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "int");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Month",
|
||||||
|
table: "BudgetPeriods",
|
||||||
|
type: "int",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "int");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Year",
|
||||||
|
table: "BudgetPeriods",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "int",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "Month",
|
||||||
|
table: "BudgetPeriods",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "int",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,6 +181,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BudgetCategoryId");
|
b.HasIndex("BudgetCategoryId");
|
||||||
@@ -199,6 +202,9 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("DefaultCategory")
|
b.Property<string>("DefaultCategory")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("DefaultPaymentStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IncludeInSummary")
|
b.Property<bool>("IncludeInSummary")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -220,13 +226,16 @@ namespace Aberwyn.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("Month")
|
b.Property<int?>("Month")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int>("Order")
|
b.Property<int>("Order")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("Year")
|
b.Property<int?>("Year")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
@@ -258,6 +267,54 @@ namespace Aberwyn.Migrations
|
|||||||
b.ToTable("Ingredients");
|
b.ToTable("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Item")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Quantity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabEntryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabEntryId");
|
||||||
|
|
||||||
|
b.ToTable("LabIngredients");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Item")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Quantity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabVersionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabVersionId");
|
||||||
|
|
||||||
|
b.ToTable("LabVersionIngredients");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -267,9 +324,6 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<string>("CarbType")
|
b.Property<string>("CarbType")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
@@ -291,6 +345,12 @@ namespace Aberwyn.Migrations
|
|||||||
b.Property<bool>("IsAvailable")
|
b.Property<bool>("IsAvailable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPublished")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<int?>("MealCategoryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -306,9 +366,82 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealCategoryId");
|
||||||
|
|
||||||
b.ToTable("Meals");
|
b.ToTable("Meals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Color")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Icon")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MealCategories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Color = "#f97316",
|
||||||
|
DisplayOrder = 1,
|
||||||
|
Icon = "🍕",
|
||||||
|
IsActive = true,
|
||||||
|
Name = "Pizza",
|
||||||
|
Slug = "pizza"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealRating", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("MealId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MealId");
|
||||||
|
|
||||||
|
b.ToTable("MealRatings");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -356,17 +489,92 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PizzaOrderId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("UserId")
|
b.Property<string>("UserId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PizzaOrderId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("PushSubscribers");
|
b.ToTable("PushSubscribers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("BaseMealId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Inspiration")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("Rating")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Tags")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("TestedBy")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BaseMealId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Instructions")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("RecipeLabEntryId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ResultNotes")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("VersionLabel")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RecipeLabEntryId");
|
||||||
|
|
||||||
|
b.ToTable("RecipeLabVersions");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -636,7 +844,7 @@ namespace Aberwyn.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
|
b.HasOne("Aberwyn.Models.BudgetCategory", null)
|
||||||
.WithMany("Items")
|
.WithMany("Items")
|
||||||
.HasForeignKey("BudgetCategoryId")
|
.HasForeignKey("BudgetCategoryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -646,8 +854,6 @@ namespace Aberwyn.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("BudgetItemDefinitionId");
|
.HasForeignKey("BudgetItemDefinitionId");
|
||||||
|
|
||||||
b.Navigation("BudgetCategory");
|
|
||||||
|
|
||||||
b.Navigation("BudgetItemDefinition");
|
b.Navigation("BudgetItemDefinition");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -660,17 +866,85 @@ namespace Aberwyn.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||||
|
.WithMany("Ingredients")
|
||||||
|
.HasForeignKey("RecipeLabEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Entry");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabVersion", "Version")
|
||||||
|
.WithMany("Ingredients")
|
||||||
|
.HasForeignKey("RecipeLabVersionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Version");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.MealCategory", "Category")
|
||||||
|
.WithMany("Meals")
|
||||||
|
.HasForeignKey("MealCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealRating", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.Meal", "Meal")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MealId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Meal");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
|
||||||
{
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PizzaOrderId");
|
||||||
|
|
||||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("PizzaOrder");
|
||||||
|
|
||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BaseMealId");
|
||||||
|
|
||||||
|
b.Navigation("BaseMeal");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
|
||||||
|
.WithMany("Versions")
|
||||||
|
.HasForeignKey("RecipeLabEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Entry");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
|
||||||
@@ -764,6 +1038,23 @@ namespace Aberwyn.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("Ingredients");
|
b.Navigation("Ingredients");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Meals");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ingredients");
|
||||||
|
|
||||||
|
b.Navigation("Versions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ingredients");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ namespace Aberwyn.Models
|
|||||||
public class BudgetPeriod
|
public class BudgetPeriod
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int Year { get; set; }
|
public int? Year { get; set; }
|
||||||
public int Month { get; set; }
|
public int? Month { get; set; }
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
public List<BudgetCategory> Categories { get; set; } = new();
|
public List<BudgetCategory> Categories { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +49,24 @@ namespace Aberwyn.Models
|
|||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
[ValidateNever]
|
[ValidateNever]
|
||||||
public BudgetCategory BudgetCategory { get; set; }
|
|
||||||
|
|
||||||
|
public PaymentStatus PaymentStatus { get; set; } = PaymentStatus.None;
|
||||||
|
|
||||||
|
}
|
||||||
|
public enum PaymentStatus
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Due = 1,
|
||||||
|
Paid = 2
|
||||||
}
|
}
|
||||||
// DTOs/BudgetDto.cs
|
// DTOs/BudgetDto.cs
|
||||||
public class BudgetDto
|
public class BudgetDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int Year { get; set; }
|
public string? Name { get; set; }
|
||||||
public int Month { get; set; }
|
public int? Year { get; set; }
|
||||||
|
public int? Month { get; set; }
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
public List<BudgetCategoryDto> Categories { get; set; } = new();
|
public List<BudgetCategoryDto> Categories { get; set; } = new();
|
||||||
@@ -69,7 +81,7 @@ namespace Aberwyn.Models
|
|||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
public int? BudgetCategoryDefinitionId { get; set; }
|
public int? BudgetCategoryDefinitionId { get; set; }
|
||||||
|
|
||||||
|
public int? BudgetPeriodId { get; set; }
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
public int Month { get; set; }
|
public int Month { get; set; }
|
||||||
}
|
}
|
||||||
@@ -84,6 +96,7 @@ namespace Aberwyn.Models
|
|||||||
|
|
||||||
public bool IsExpense { get; set; }
|
public bool IsExpense { get; set; }
|
||||||
public bool IncludeInSummary { get; set; }
|
public bool IncludeInSummary { get; set; }
|
||||||
|
public PaymentStatus PaymentStatus { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -91,6 +104,11 @@ namespace Aberwyn.Models
|
|||||||
{
|
{
|
||||||
public List<BudgetItem> BudgetItems { get; set; } = new List<BudgetItem>();
|
public List<BudgetItem> BudgetItems { get; set; } = new List<BudgetItem>();
|
||||||
}
|
}
|
||||||
|
public class PaymentStatusUpdateDto
|
||||||
|
{
|
||||||
|
public int ItemId { get; set; }
|
||||||
|
public PaymentStatus Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class BudgetItemDefinition
|
public class BudgetItemDefinition
|
||||||
{
|
{
|
||||||
@@ -99,6 +117,8 @@ namespace Aberwyn.Models
|
|||||||
public string? DefaultCategory { get; set; } // valfritt, kan föreslå "Bilar"
|
public string? DefaultCategory { get; set; } // valfritt, kan föreslå "Bilar"
|
||||||
public bool IsExpense { get; set; }
|
public bool IsExpense { get; set; }
|
||||||
public bool IncludeInSummary { get; set; }
|
public bool IncludeInSummary { get; set; }
|
||||||
|
public PaymentStatus? DefaultPaymentStatus { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
public class BudgetCategoryDefinition
|
public class BudgetCategoryDefinition
|
||||||
{
|
{
|
||||||
|
|||||||
33
Aberwyn/Models/MediaModels.cs
Normal file
33
Aberwyn/Models/MediaModels.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Aberwyn.Models
|
||||||
|
{
|
||||||
|
public class StreamingService
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } // T.ex. "Netflix", "Plex"
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MediaItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string TmdbId { get; set; } // TMDb ID
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public string PosterPath { get; set; }
|
||||||
|
public string MediaType { get; set; } // "movie" eller "tv"
|
||||||
|
public double Rating { get; set; }
|
||||||
|
public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
public List<MediaItemStreamingService> MediaItemStreamingServices { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MediaItemStreamingService
|
||||||
|
{
|
||||||
|
public int MediaItemId { get; set; }
|
||||||
|
public MediaItem MediaItem { get; set; }
|
||||||
|
|
||||||
|
public int StreamingServiceId { get; set; }
|
||||||
|
public StreamingService StreamingService { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ namespace Aberwyn.Models
|
|||||||
public int Year { get; set; } // Year for the menu
|
public int Year { get; set; } // Year for the menu
|
||||||
}
|
}
|
||||||
public class WeeklyMenuDto
|
public class WeeklyMenuDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public int DayOfWeek { get; set; }
|
public int DayOfWeek { get; set; }
|
||||||
@@ -43,6 +43,9 @@ public class WeeklyMenu
|
|||||||
public int WeekNumber { get; set; }
|
public int WeekNumber { get; set; }
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
[NotMapped] public byte[]? BreakfastThumbnail { get; set; }
|
||||||
|
[NotMapped] public byte[]? LunchThumbnail { get; set; }
|
||||||
|
[NotMapped] public byte[]? DinnerThumbnail { get; set; }
|
||||||
|
|
||||||
[NotMapped] public string? BreakfastMealName { get; set; }
|
[NotMapped] public string? BreakfastMealName { get; set; }
|
||||||
[NotMapped] public string? LunchMealName { get; set; }
|
[NotMapped] public string? LunchMealName { get; set; }
|
||||||
@@ -70,7 +73,10 @@ public class WeeklyMenu
|
|||||||
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? ProteinType { get; set; }
|
public string? ProteinType { get; set; }
|
||||||
public string? Category { get; set; }
|
public int? MealCategoryId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("MealCategoryId")]
|
||||||
|
public MealCategory? Category { get; set; }
|
||||||
public string? CarbType { get; set; }
|
public string? CarbType { get; set; }
|
||||||
public string? RecipeUrl { get; set; }
|
public string? RecipeUrl { get; set; }
|
||||||
public string? ImageUrl { get; set; }
|
public string? ImageUrl { get; set; }
|
||||||
@@ -78,7 +84,7 @@ public class WeeklyMenu
|
|||||||
public bool IsAvailable { get; set; }
|
public bool IsAvailable { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public byte[]? ThumbnailData { get; set; }
|
public byte[]? ThumbnailData { get; set; }
|
||||||
|
public bool IsPublished { get; set; } = false;
|
||||||
public byte[]? ImageData { get; set; }
|
public byte[]? ImageData { get; set; }
|
||||||
public string? ImageMimeType { get; set; }
|
public string? ImageMimeType { get; set; }
|
||||||
public string? Instructions { get; set; }
|
public string? Instructions { get; set; }
|
||||||
@@ -119,24 +125,74 @@ public class WeeklyMenu
|
|||||||
|
|
||||||
}
|
}
|
||||||
public class MealListDto
|
public class MealListDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? ThumbnailData { get; set; }
|
public string? ThumbnailData { get; set; }
|
||||||
|
|
||||||
public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false)
|
public List<IngredientDto> Ingredients { get; set; } = new();
|
||||||
|
|
||||||
|
public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false)
|
||||||
|
{
|
||||||
|
return new MealListDto
|
||||||
{
|
{
|
||||||
return new MealListDto
|
Id = meal.Id,
|
||||||
|
Name = meal.Name,
|
||||||
|
Description = meal.Description,
|
||||||
|
ThumbnailData = includeThumbnail && meal.ThumbnailData != null
|
||||||
|
? Convert.ToBase64String(meal.ThumbnailData)
|
||||||
|
: null,
|
||||||
|
Ingredients = meal.Ingredients?.Select(i => new IngredientDto
|
||||||
{
|
{
|
||||||
Id = meal.Id,
|
Item = i.Item
|
||||||
Name = meal.Name,
|
}).ToList() ?? new List<IngredientDto>()
|
||||||
Description = meal.Description,
|
};
|
||||||
ThumbnailData = includeThumbnail && meal.ThumbnailData != null
|
}
|
||||||
? Convert.ToBase64String(meal.ThumbnailData)
|
|
||||||
: null
|
public class IngredientDto
|
||||||
};
|
{
|
||||||
}
|
public string Item { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MealCategory
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string? Slug { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
public string? Color { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public int DisplayOrder { get; set; } = 0;
|
||||||
|
[NotMapped] // krävs om du inte har kolumnen i databasen
|
||||||
|
public int MealCount { get; set; }
|
||||||
|
|
||||||
|
public List<Meal> Meals { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MealRating
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int MealId { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public int Rating { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public Meal Meal { get; set; }
|
||||||
|
}
|
||||||
|
public class MealRatingDto
|
||||||
|
{
|
||||||
|
public int MealId { get; set; }
|
||||||
|
public int Rating { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
public string Auth { get; set; }
|
public string Auth { get; set; }
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
public virtual ApplicationUser User { get; set; }
|
public virtual ApplicationUser User { get; set; }
|
||||||
|
public int? PizzaOrderId { get; set; }
|
||||||
|
public virtual PizzaOrder PizzaOrder { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
public class PushMessageDto
|
public class PushMessageDto
|
||||||
{
|
{
|
||||||
|
|||||||
81
Aberwyn/Models/RecipeLab.cs
Normal file
81
Aberwyn/Models/RecipeLab.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Aberwyn.Models
|
||||||
|
{
|
||||||
|
public class RecipeLabEntry
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public string? Category { get; set; }
|
||||||
|
public string? Inspiration { get; set; }
|
||||||
|
|
||||||
|
public int? BaseMealId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("BaseMealId")]
|
||||||
|
public Meal? BaseMeal { get; set; }
|
||||||
|
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
public int? Rating { get; set; }
|
||||||
|
public string? TestedBy { get; set; }
|
||||||
|
public string? Tags { get; set; } // "vego,snabbt"
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||||
|
public List<LabIngredient> Ingredients { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
public List<RecipeLabVersion> Versions { get; set; } = new();
|
||||||
|
}
|
||||||
|
public class RecipeLabVersion
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int RecipeLabEntryId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("RecipeLabEntryId")]
|
||||||
|
public RecipeLabEntry Entry { get; set; }
|
||||||
|
|
||||||
|
public string VersionLabel { get; set; } = "v1";
|
||||||
|
|
||||||
|
public List<LabVersionIngredient> Ingredients { get; set; } = new();
|
||||||
|
|
||||||
|
public string? Instructions { get; set; }
|
||||||
|
public string? ResultNotes { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class LabIngredient
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int RecipeLabEntryId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("RecipeLabEntryId")]
|
||||||
|
public RecipeLabEntry Entry { get; set; }
|
||||||
|
|
||||||
|
public string Quantity { get; set; } = "";
|
||||||
|
public string Item { get; set; } = "";
|
||||||
|
}
|
||||||
|
public class LabVersionIngredient
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int RecipeLabVersionId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("RecipeLabVersionId")]
|
||||||
|
public RecipeLabVersion Version { get; set; }
|
||||||
|
|
||||||
|
public string Quantity { get; set; } = "";
|
||||||
|
public string Item { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -191,14 +191,6 @@ app.Use(async (context, next) =>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline
|
|
||||||
if (!app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseExceptionHandler("/Home/Error");
|
|
||||||
app.UseHsts();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseSession();
|
app.UseSession();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
@@ -211,6 +203,11 @@ app.MapControllerRoute(
|
|||||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||||
app.MapRazorPages();
|
app.MapRazorPages();
|
||||||
|
|
||||||
|
app.UseExceptionHandler("/Error");
|
||||||
|
app.UseStatusCodePagesWithReExecute("/Error/{0}");
|
||||||
|
app.UseHsts();
|
||||||
|
|
||||||
|
|
||||||
// Init: migrera databas och skapa admin
|
// Init: migrera databas och skapa admin
|
||||||
if (setup.IsConfigured)
|
if (setup.IsConfigured)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.todo-board {
|
.todo-board {
|
||||||
padding: 20px;
|
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
.todo-columns {
|
.todo-columns {
|
||||||
|
|||||||
@@ -5,232 +5,275 @@
|
|||||||
ViewData["Title"] = "Budget";
|
ViewData["Title"] = "Budget";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div ng-app="budgetApp" ng-controller="BudgetController" class="budget-page" ng-init="loadBudget()">
|
<div ng-app="budgetApp" ng-controller="BudgetController">
|
||||||
<div class="budget-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
<div class="budget-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
||||||
<div class="month-nav-bar" style="display: flex; align-items: center; gap: 10px; position: relative;">
|
<div class="month-nav-bar" style="display: flex; align-items: center; gap: 10px; position: relative;">
|
||||||
<button class="nav-button" ng-click="previousMonth()">←</button>
|
<button class="nav-button" ng-click="previousMonth()">←</button>
|
||||||
<span class="month-label" ng-click="showMonthPicker = !showMonthPicker" style="cursor: pointer;">
|
<span class="month-label" ng-click="showMonthPicker = !showMonthPicker" style="cursor: pointer;">
|
||||||
{{ selectedMonthName }} {{ selectedYear }}
|
{{ selectedMonthName }} {{ selectedYear }}
|
||||||
</span>
|
</span>
|
||||||
<button class="nav-button" ng-click="nextMonth()">→</button>
|
<button class="nav-button" ng-click="nextMonth()">→</button>
|
||||||
|
|
||||||
<div class="month-picker-dropdown" ng-show="showMonthPicker" style="position: absolute; top: 100%; left: 50%; transform: translateX(-50%); background: white; border: 1px solid #ccc; padding: 10px; border-radius: 8px; z-index: 1000; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
|
<div class="month-picker-dropdown" ng-show="showMonthPicker" style="position: absolute; top: 100%; left: 50%; transform: translateX(-50%); background: white; border: 1px solid #ccc; padding: 10px; border-radius: 8px; z-index: 1000; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
|
||||||
<select ng-model="tempMonth" ng-options="month for month in monthNames" style="margin-bottom: 8px;"></select><br>
|
<select ng-model="tempMonth" ng-options="month for month in monthNames" style="margin-bottom: 8px;"></select><br>
|
||||||
<input type="number" ng-model="tempYear" placeholder="År" style="width: 100%; margin-bottom: 8px;" />
|
<input type="number" ng-model="tempYear" placeholder="År" style="width: 100%; margin-bottom: 8px;" />
|
||||||
<button class="nav-button" ng-click="applyMonthSelection()">Välj</button>
|
<button class="nav-button" ng-click="applyMonthSelection()">Välj</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-container" ng-class="{ 'open': menuOpen }">
|
|
||||||
<button class="icon-button" ng-click="toggleMenu($event)">
|
|
||||||
<i class="fa fa-ellipsis-v"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" ng-show="menuOpen">
|
|
||||||
<button ng-click="copyPreviousMonthSafe()">Kopiera föregående månad</button>
|
|
||||||
|
|
||||||
<button ng-click="deleteMonth(); menuOpen = false;" class="danger">Ta bort hela månaden</button>
|
|
||||||
<button ng-click="createNewCategory(); menuOpen = false;">Lägg till ny kategori</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="budget-overview-row" ng-if="budget && budget.categories.length > 0">
|
|
||||||
<!-- Vänster: Summering -->
|
|
||||||
<div class="budget-summary-box compact">
|
|
||||||
<h3>Sammanställning</h3>
|
|
||||||
<ul class="summary-list">
|
|
||||||
<li><span>💰 Inkomst:</span> {{ getTotalIncome() | number:0 }} kr</li>
|
|
||||||
<li><span>💸 Utgift:</span> {{ getTotalExpense() | number:0 }} kr</li>
|
|
||||||
<li><span>🏦 Sparande:</span> {{ getTotalSaving() | number:0 }} kr</li>
|
|
||||||
<p>
|
|
||||||
📈 Kvar:
|
|
||||||
<span class="highlight leftover" ng-class="{'plus': getLeftover() >= 0, 'minus': getLeftover() < 0}">
|
|
||||||
{{ getLeftover() | number:0 }} kr
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<hr>
|
|
||||||
<ul class="category-summary-list">
|
|
||||||
<li ng-repeat="cat in budget.categories">
|
|
||||||
<span style="color: {{ cat.color }}">{{ cat.name }}</span>
|
|
||||||
<span>{{ getCategorySum(cat) | number:0 }} kr</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Höger: Diagramväxling -->
|
|
||||||
<div class="budget-chart-box compact">
|
|
||||||
<div class="chart-row">
|
|
||||||
<div class="chart-legend">
|
|
||||||
<h4>Utgifter</h4>
|
|
||||||
|
|
||||||
<p style="font-size: 12px; margin: 4px 0 10px;">
|
|
||||||
Totalt: <strong>{{ getTotalExpense() | number:0 }} kr</strong>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p ng-if="topExpenseCategory" style="font-size: 12px; margin-bottom: 12px;">
|
|
||||||
Största kategori: <br>
|
|
||||||
<strong>{{ topExpenseCategory.name }}</strong> ({{ topExpenseCategory.percent | number:1 }}%)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-area">
|
|
||||||
<canvas id="expenseChart"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="budget-grid" ng-if="budget && budget.categories">
|
|
||||||
<div class="budget-card"
|
|
||||||
ng-repeat="cat in budget.categories"
|
|
||||||
style="--cat-color: {{cat.color}};"
|
|
||||||
draggable-category
|
|
||||||
category="cat"
|
|
||||||
on-category-drop="handleCategoryDrop(data, targetCategory)">
|
|
||||||
|
|
||||||
<div class="card-header" style="background-color: {{cat.color}};">
|
|
||||||
<div class="header-left" ng-if="!cat.editing">{{ cat.name }}</div>
|
|
||||||
<input class="header-edit" type="text" ng-model="cat.name" ng-if="cat.editing" />
|
|
||||||
<input class="color-edit" type="color" ng-model="cat.color" ng-if="cat.editing" />
|
|
||||||
<div class="header-actions">
|
|
||||||
<button class="icon-button" ng-click="cat.editing ? saveCategory(cat) : cat.editing = true">
|
|
||||||
<i ng-class="{'fa fa-check': cat.editing, 'fa fa-pencil': !cat.editing}"></i>
|
|
||||||
</button>
|
|
||||||
<button class="icon-button" ng-if="cat.editing" ng-click="cancelCategoryEdit(cat)">
|
|
||||||
<i class="fa fa-times"></i>
|
|
||||||
</button>
|
|
||||||
<button class="icon-button" ng-if="cat.editing" ng-click="cat.allowDrag = !cat.allowDrag" title="Flytt av kategori">
|
|
||||||
<i class="fa" ng-class="cat.allowDrag ? 'fa-unlock' : 'fa-lock'"></i>
|
|
||||||
</button>
|
|
||||||
<button class="icon-button danger" ng-if="cat.editing" ng-click="deleteCategory(cat)">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-list">
|
|
||||||
<div ng-repeat="item in cat.items track by item.id">
|
<div class="menu-container" ng-class="{ 'open': menuOpen }">
|
||||||
<div drop-placeholder
|
<button class="icon-button" ng-click="toggleMenu($event)">
|
||||||
category="cat"
|
<i class="fa fa-ellipsis-v"></i>
|
||||||
index="$index"
|
</button>
|
||||||
on-drop-item="handleItemPreciseDrop(data, cat, $index)">
|
<div class="dropdown-menu" ng-show="menuOpen">
|
||||||
|
<button ng-click="copyPreviousMonthSafe()">Kopiera föregående månad</button>
|
||||||
|
<button ng-click="deleteMonth(); menuOpen = false;" class="danger">Ta bort hela månaden</button>
|
||||||
|
<button ng-click="createNewCategory(); menuOpen = false;">Lägg till ny kategori</button>
|
||||||
|
<!--<button ng-click="openImportModule(); menuOpen = false;">📥 Importera rader</button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="budget-overview-row" ng-if="budget && budget.categories.length > 0">
|
||||||
|
<!-- Vänster: Summering -->
|
||||||
|
<div class="budget-summary-box compact">
|
||||||
|
<h3>Sammanställning</h3>
|
||||||
|
<ul class="summary-list">
|
||||||
|
<li><span>💰 Inkomst:</span> {{ getTotalIncome() | number:0 }} kr</li>
|
||||||
|
<li><span>💸 Utgift:</span> {{ getTotalExpense() | number:0 }} kr</li>
|
||||||
|
<li><span>🏦 Sparande:</span> {{ getTotalSaving() | number:0 }} kr</li>
|
||||||
|
<p>
|
||||||
|
📈 Kvar:
|
||||||
|
<span class="highlight leftover" ng-class="{'plus': getLeftover() >= 0, 'minus': getLeftover() < 0}">
|
||||||
|
{{ getLeftover() | number:0 }} kr
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<ul class="category-summary-list">
|
||||||
|
<li ng-repeat="cat in budget.categories">
|
||||||
|
<span style="color: {{ cat.color }}">{{ cat.name }}</span>
|
||||||
|
<span>{{ getCategorySum(cat) | number:0 }} kr</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Höger: Diagramväxling -->
|
||||||
|
<div class="budget-chart-box compact">
|
||||||
|
<div class="chart-row">
|
||||||
|
<div class="chart-legend">
|
||||||
|
<h4>Utgifter</h4>
|
||||||
|
|
||||||
|
<p style="font-size: 12px; margin: 4px 0 10px;">
|
||||||
|
Totalt: <strong>{{ getTotalExpense() | number:0 }} kr</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p ng-if="topExpenseCategory" style="font-size: 12px; margin-bottom: 12px;">
|
||||||
|
Största kategori: <br>
|
||||||
|
<strong>{{ topExpenseCategory.name }}</strong> ({{ topExpenseCategory.percent | number:1 }}%)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-area">
|
||||||
|
<canvas id="expenseChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="budget-grid" ng-if="budget && budget.categories">
|
||||||
|
<div class="budget-card"
|
||||||
|
ng-repeat="cat in budget.categories"
|
||||||
|
style="--cat-color: {{cat.color}};"
|
||||||
|
draggable-category
|
||||||
|
category="cat"
|
||||||
|
on-category-drop="handleCategoryDrop(data, targetCategory)">
|
||||||
|
|
||||||
|
<div class="card-header" style="background-color: {{cat.color}};">
|
||||||
|
<div class="header-left" ng-if="!cat.editing" >{{ cat.name }}</div>
|
||||||
|
<input class="header-edit" type="text" ng-model="cat.name" ng-if="cat.editing" />
|
||||||
|
<input class="color-edit" type="color" ng-model="cat.color" ng-if="cat.editing" />
|
||||||
|
<div class="header-actions">
|
||||||
|
<button class="icon-button" ng-click="cat.editing ? saveCategory(cat) : cat.editing = true">
|
||||||
|
<i ng-class="{'fa fa-check': cat.editing, 'fa fa-pencil': !cat.editing}"></i>
|
||||||
|
</button>
|
||||||
|
<button class="icon-button" ng-if="cat.editing" ng-click="cancelCategoryEdit(cat)">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</button>
|
||||||
|
<button class="icon-button" ng-if="cat.editing" ng-click="cat.allowDrag = !cat.allowDrag" title="Flytt av kategori">
|
||||||
|
<i class="fa" ng-class="cat.allowDrag ? 'fa-unlock' : 'fa-lock'"></i>
|
||||||
|
</button>
|
||||||
|
<button class="icon-button danger" ng-if="cat.editing" ng-click="deleteCategory(cat)">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item-row"
|
<div class="item-list">
|
||||||
draggable-item
|
<div ng-repeat="item in cat.items track by item.id">
|
||||||
item="item"
|
<div drop-placeholder
|
||||||
category="cat"
|
category="cat"
|
||||||
on-item-drop="handleItemDrop(event, data, cat)"
|
index="$index"
|
||||||
ng-class="{ dragging: dragInProgress && draggedItemId === item.id }">
|
on-drop-item="handleItemPreciseDrop(data, cat, $index)">
|
||||||
<i class="fa fa-grip-lines drag-handle"
|
</div>
|
||||||
ng-show="cat.editing"
|
|
||||||
style="opacity: 0.5; padding-right: 6px; cursor: grab;"></i>
|
|
||||||
|
|
||||||
<input type="text" ng-model="item.name" ng-if="cat.editing" />
|
<div class="item-row"
|
||||||
<span ng-if="!cat.editing" title="{{ item.definitionName }}">{{ item.name }}</span>
|
draggable-item
|
||||||
<!-- <span ng-if="!cat.editing">#{{ item.definitionName }}</span>-->
|
item="item"
|
||||||
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
|
category="cat"
|
||||||
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
|
on-item-drop="handleItemDrop(event, data, cat)"
|
||||||
|
ng-class="{
|
||||||
|
dragging: dragInProgress && draggedItemId === item.id,
|
||||||
|
due: item.paymentStatus === 1,
|
||||||
|
paid: item.paymentStatus === 2
|
||||||
|
}"
|
||||||
|
ng-click="handleItemInteraction($event, item)">
|
||||||
|
<i class="fa fa-grip-lines drag-handle"
|
||||||
|
ng-show="cat.editing"
|
||||||
|
style="opacity: 0.5; padding-right: 6px; cursor: grab;"></i>
|
||||||
|
|
||||||
<!-- 3-pricksmeny -->
|
<input type="text" class="item-label" ng-model="item.name" ng-if="cat.editing" />
|
||||||
<div class="item-menu-container" ng-if="cat.editing" style="position: relative;">
|
<span ng-if="!cat.editing" class="item-label" title="{{ item.name }}">{{ item.name }}</span>
|
||||||
<button class="icon-button" ng-click="openItemMenu($event, item)">
|
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
|
||||||
<i class="fa fa-ellipsis-v"></i>
|
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
|
||||||
</button>
|
|
||||||
<div class="item-floating-menu" ng-show="menuVisible" ng-style="menuStyle">
|
<!-- 3-pricksmeny -->
|
||||||
<span style="font-size: 11px; color: #94a3b8; display: block; margin: 0px 0px 0px 40px;">
|
<div class="item-menu-container" ng-if="cat.editing" style="position: relative;">
|
||||||
#{{ menuItem.definitionName }}
|
<button class="icon-button" ng-click="openItemMenu($event, item)">
|
||||||
</span>
|
<i class="fa fa-ellipsis-v"></i>
|
||||||
<button ng-click="deleteItem(menuItem.category, menuItem)">🗑 Ta bort</button>
|
|
||||||
<hr>
|
|
||||||
<button
|
|
||||||
ng-click="setItemType(menuItem, 'expense')"
|
|
||||||
ng-class="{ 'disabled': menuItem.isExpense }">
|
|
||||||
<span ng-if="menuItem.isExpense">✔️</span> 💸 Utgift
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-click="setItemType(menuItem, 'income')"
|
|
||||||
ng-class="{ 'disabled': !menuItem.isExpense && menuItem.includeInSummary }">
|
|
||||||
<span ng-if="!menuItem.isExpense && menuItem.includeInSummary">✔️</span> 💰 Inkomst
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-click="setItemType(menuItem, 'saving')"
|
|
||||||
ng-class="{ 'disabled': !menuItem.isExpense && !menuItem.includeInSummary }">
|
|
||||||
<span ng-if="!menuItem.isExpense && !menuItem.includeInSummary">✔️</span> 🏦 Sparande
|
|
||||||
</button>
|
</button>
|
||||||
|
<div class="item-floating-menu" ng-show="menuVisible" ng-style="menuStyle">
|
||||||
|
<span style="font-size: 11px; color: #94a3b8; display: block; margin: 0px 0px 0px 40px;">
|
||||||
|
#{{ menuItem.definitionName }}
|
||||||
|
</span>
|
||||||
|
<button ng-click="deleteItem(menuItem.category, menuItem)">🗑 Ta bort</button>
|
||||||
|
<hr>
|
||||||
|
<button
|
||||||
|
ng-click="setItemType(menuItem, 'expense')"
|
||||||
|
ng-class="{ 'disabled': menuItem.isExpense }">
|
||||||
|
<span ng-if="menuItem.isExpense">✔️</span> 💸 Utgift
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ng-click="setItemType(menuItem, 'income')"
|
||||||
|
ng-class="{ 'disabled': !menuItem.isExpense && menuItem.includeInSummary }">
|
||||||
|
<span ng-if="!menuItem.isExpense && menuItem.includeInSummary">✔️</span> 💰 Inkomst
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ng-click="setItemType(menuItem, 'saving')"
|
||||||
|
ng-class="{ 'disabled': !menuItem.isExpense && !menuItem.includeInSummary }">
|
||||||
|
<span ng-if="!menuItem.isExpense && !menuItem.includeInSummary">✔️</span> 🏦 Sparande
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div drop-fallback
|
||||||
|
ng-if="cat.editing"
|
||||||
|
category="cat"
|
||||||
|
on-drop-item="handleItemPreciseDrop(data, targetCategory, targetIndex)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="icon-button add-post-btn"
|
||||||
|
ng-if="cat.editing && !cat.addingItem"
|
||||||
|
ng-click="openItemPopup($event, cat)">
|
||||||
|
➕ Lägg till post
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div drop-fallback
|
<div class="item-row total-row">
|
||||||
ng-if="cat.editing"
|
<div>Summa</div>
|
||||||
category="cat"
|
<div class="amount">{{ getCategorySum(cat) | number:0 }}</div>
|
||||||
on-drop-item="handleItemPreciseDrop(data, targetCategory, targetIndex)">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="icon-button add-post-btn"
|
|
||||||
ng-if="cat.editing && !cat.addingItem"
|
|
||||||
ng-click="openItemPopup($event, cat)">
|
|
||||||
➕ Lägg till post
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row total-row">
|
|
||||||
<div>Summa</div>
|
|
||||||
<div class="amount">{{ getCategorySum(cat) | number:0 }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
|
||||||
Det finns ingen budgetdata för vald månad ({{ selectedMonth }}/{{ selectedYear }}).
|
|
||||||
<button ng-click="copyPreviousMonthSafe()">[Test] Kopiera föregående</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
||||||
|
<p>Det finns ingen budgetdata för
|
||||||
|
<strong>{{ budget.name || (selectedMonth + '/' + selectedYear) }}</strong>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="add-item-popup" ng-show="addPopupVisible" ng-style="addPopupStyle" ng-class="{ 'above': addPopupAbove }">
|
<div style="margin-top: 10px;">
|
||||||
<label>Typ:</label>
|
<button ng-click="createEmptyBudget()" style="margin-right: 10px;">
|
||||||
<select ng-model="addPopupData.newItemType">
|
Skapa ny budget
|
||||||
<option value="expense">💸 Utgift</option>
|
</button>
|
||||||
<option value="income">💰 Inkomst</option>
|
<button ng-click="copyPreviousMonthSafe()">
|
||||||
<option value="saving">🏦 Sparande</option>
|
Kopiera föregående månad
|
||||||
</select>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="add-item-popup" ng-show="addPopupVisible" ng-style="addPopupStyle" ng-class="{ 'above': addPopupAbove }">
|
||||||
|
<label>Typ:</label>
|
||||||
|
<select ng-model="addPopupData.newItemType">
|
||||||
|
<option value="expense">💸 Utgift</option>
|
||||||
|
<option value="income">💰 Inkomst</option>
|
||||||
|
<option value="saving">🏦 Sparande</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<label>Definition:</label>
|
<label>Definition:</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
ng-model="addPopupData.newItemDefinition"
|
ng-model="addPopupData.newItemDefinition"
|
||||||
ng-change="updateDefinitionSuggestions()"
|
ng-change="updateDefinitionSuggestions()"
|
||||||
placeholder="Ex: Elhandel"
|
placeholder="Ex: Elhandel"
|
||||||
autocomplete="on"
|
autocomplete="on"
|
||||||
ng-blur="hideSuggestionsDelayed()"
|
ng-blur="hideSuggestionsDelayed()"
|
||||||
ng-focus="showDefinitionSuggestions = true" />
|
ng-focus="showDefinitionSuggestions = true" />
|
||||||
<p style="color: red">{{ filteredDefinitions.length }} träffar</p>
|
<p style="color: red">{{ filteredDefinitions.length }} träffar</p>
|
||||||
<ul class="suggestion-list" ng-show="showDefinitionSuggestions && filteredDefinitions.length > 0">
|
<ul class="suggestion-list" ng-show="showDefinitionSuggestions && filteredDefinitions.length > 0">
|
||||||
<li ng-repeat="suggestion in filteredDefinitions"
|
<li ng-repeat="suggestion in filteredDefinitions"
|
||||||
ng-mousedown="selectDefinitionSuggestion(suggestion.Name)">
|
ng-mousedown="selectDefinitionSuggestion(suggestion.Name)">
|
||||||
{{ suggestion.Name }}
|
{{ suggestion.Name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<label>Etikett (valfritt):</label>
|
<label>Etikett (valfritt):</label>
|
||||||
<input type="text" ng-model="addPopupData.newItemLabel" placeholder="Ex: Däck till Volvon" />
|
<input type="text" ng-model="addPopupData.newItemLabel" placeholder="Ex: Däck till Volvon" />
|
||||||
|
|
||||||
<label>Belopp:</label>
|
<label>Belopp:</label>
|
||||||
<input type="number" ng-model="addPopupData.newItemAmount" placeholder="0" />
|
<input type="number" ng-model="addPopupData.newItemAmount" placeholder="0" />
|
||||||
|
|
||||||
<button ng-click="addItemFromPopup()">Lägg till</button>
|
<button ng-click="addItemFromPopup()">Lägg till</button>
|
||||||
<button ng-click="addPopupVisible = false">Avbryt</button>
|
<button ng-click="addPopupVisible = false">Avbryt</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="import-module" ng-show="importing" style="position: fixed; top: 10vh; left: 50%; transform: translateX(-50%);
|
||||||
|
background: #1F2C3C; color: white; padding: 24px; border-radius: 8px; z-index: 2000; width: 90%; max-width: 600px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.4);">
|
||||||
|
|
||||||
|
<h3 style="margin-top: 0;">📥 Importera rader till {{ importTargetCategory.name }}</h3>
|
||||||
|
|
||||||
|
<label for="importText">Klistra in rader (en per rad, t.ex. <code>Kallhyra 11315</code>):</label>
|
||||||
|
<textarea id="importText" ng-model="importText" rows="6" style="width: 100%; margin-bottom: 10px;"></textarea>
|
||||||
|
|
||||||
|
<button ng-click="parseImportText()">Förhandsgranska</button>
|
||||||
|
<button ng-click="cancelImport()">Avbryt</button>
|
||||||
|
|
||||||
|
<div class="preview-list" ng-if="importPreview.length > 0" style="margin-top: 16px;">
|
||||||
|
<h4>Förhandsvisning:</h4>
|
||||||
|
<div class="item-row" ng-repeat="item in importPreview">
|
||||||
|
<span class="item-label">{{ item.name }}</span>
|
||||||
|
<span class="amount">{{ item.amount | number:0 }} kr</span>
|
||||||
|
</div>
|
||||||
|
<button ng-click="applyImport()">✔️ Skapa {{ importPreview.length }} rader</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<link rel="stylesheet" href="~/css/budget.css" />
|
<link rel="stylesheet" href="~/css/budget.css" />
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<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" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||||
|
<script>
|
||||||
|
window.initialYear = @(ViewBag.Year ?? "null");
|
||||||
|
window.initialMonth = @(ViewBag.Month ?? "null");
|
||||||
|
window.initialName = "@(ViewBag.BudgetName ?? "")";
|
||||||
|
|
||||||
|
</script>
|
||||||
<script src="~/js/budget.js"></script>
|
<script src="~/js/budget.js"></script>
|
||||||
<script src="~/js/budget-dragdrop.js"></script>
|
<script src="~/js/budget-dragdrop.js"></script>
|
||||||
75
Aberwyn/Views/Error/Error.cshtml
Normal file
75
Aberwyn/Views/Error/Error.cshtml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
var errorCode = ViewData["ErrorCode"] as int?;
|
||||||
|
var exception = ViewData["Exception"] as Exception;
|
||||||
|
var isDebug = System.Diagnostics.Debugger.IsAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error-page {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 4rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: #1F2C3C;
|
||||||
|
color: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.4);
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
.error-page h1 {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
.error-page .code {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #facc15;
|
||||||
|
}
|
||||||
|
.error-page pre {
|
||||||
|
background-color: #2E3C4F;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #fefefe;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.error-page details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6a0dad;
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.error-page p {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="error-page">
|
||||||
|
<h1>🚨 Något gick fel</h1>
|
||||||
|
<a href="/" class="btn-outline">Till startsidan</a>
|
||||||
|
|
||||||
|
@if (errorCode.HasValue)
|
||||||
|
{
|
||||||
|
<div class="code">Statuskod: <strong>@errorCode</strong></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (exception != null)
|
||||||
|
{
|
||||||
|
<h3>Felmeddelande:</h3>
|
||||||
|
<pre>@exception.Message</pre>
|
||||||
|
|
||||||
|
@if (isDebug)
|
||||||
|
{
|
||||||
|
<details>
|
||||||
|
<summary>Visa stacktrace</summary>
|
||||||
|
<pre>@exception.StackTrace</pre>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Vi kunde tyvärr inte visa sidan. Det kan bero på ett tillfälligt fel.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Pizza Admin";
|
ViewData["Title"] = "Pizza Admin";
|
||||||
var availablePizzas = Model.AvailablePizzas as List<Meal> ?? new List<Meal>();
|
var availablePizzas = Model.AvailablePizzas as List<Meal> ?? new List<Meal>();
|
||||||
var restaurantOpen = ViewBag.RestaurantOpen as bool? ?? true;
|
var restaurantOpen = ViewBag.RestaurantIsOpen as bool? ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
<link rel="stylesheet" href="~/css/pizza-admin.css" />
|
<link rel="stylesheet" href="~/css/pizza-admin.css" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@using Newtonsoft.Json.Serialization
|
@using Newtonsoft.Json.Serialization
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Beställ pizza";
|
ViewData["Title"] = "Beställ pizza";
|
||||||
var pizzas = ViewBag.Pizzas as List<Meal>;
|
var pizzas = ViewBag.Pizzas as List<MealListDto> ?? new List<MealListDto>();
|
||||||
var previousOrder = ViewBag.PreviousOrder as PizzaOrder;
|
var previousOrder = ViewBag.PreviousOrder as PizzaOrder;
|
||||||
var lastId = Context.Session.GetInt32("LastPizzaOrderId");
|
var lastId = Context.Session.GetInt32("LastPizzaOrderId");
|
||||||
var isCurrentOrder = previousOrder?.Id == lastId;
|
var isCurrentOrder = previousOrder?.Id == lastId;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
<datalist id="meals-list">
|
<datalist id="meals-list">
|
||||||
@foreach (var meal in (List<Meal>)ViewBag.AvailableMeals)
|
@foreach (var meal in (List<Meal>)ViewBag.AvailableMeals)
|
||||||
{
|
{
|
||||||
<option value="@meal.Id">@meal.Name</option>
|
<option value="@meal.Name">@meal.Name</option>
|
||||||
}
|
}
|
||||||
</datalist>
|
</datalist>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -7,16 +7,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" ng-app="budgetApp">
|
<html lang="en" ng-app="budgetApp">
|
||||||
<head>
|
<head>
|
||||||
|
<style>
|
||||||
|
[ng-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<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/budget.css">
|
<link rel="stylesheet" type="text/css" href="~/css/budget.css">
|
||||||
</head>
|
</head>
|
||||||
<body ng-controller="BudgetController">
|
<body ng-controller="BudgetController" ng-cloak>
|
||||||
|
|
||||||
<div class="budget-page">
|
<div class="budget-page">
|
||||||
<h1>Budget Overview</h1>
|
<h1 ng-cloak>Budget Overview</h1>
|
||||||
|
|
||||||
<div class="date-picker" ng-click="toggleDatePicker($event)">
|
<div class="date-picker" ng-click="toggleDatePicker($event)">
|
||||||
<button type="button" class="date-picker-toggle">
|
<button type="button" class="date-picker-toggle">
|
||||||
@@ -28,7 +33,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="budget-container">
|
<div ng-if="isLoadingBudget" class="loading-indicator">
|
||||||
|
⏳ Laddar budget...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="!isLoadingBudget" 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 }}
|
||||||
@@ -55,7 +64,7 @@
|
|||||||
angular.module('budgetApp', [])
|
angular.module('budgetApp', [])
|
||||||
.controller('BudgetController', function ($scope, $http) {
|
.controller('BudgetController', function ($scope, $http) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
$scope.isLoadingBudget = true;
|
||||||
// Initialize months and years
|
// Initialize months and years
|
||||||
$scope.months = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
|
$scope.months = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
|
||||||
$scope.years = [...Array(11).keys()].map(i => today.getFullYear() - 10 + i);
|
$scope.years = [...Array(11).keys()].map(i => today.getFullYear() - 10 + i);
|
||||||
@@ -72,11 +81,16 @@
|
|||||||
|
|
||||||
// Automatically filter when month or year changes
|
// Automatically filter when month or year changes
|
||||||
$scope.filterBudget = function () {
|
$scope.filterBudget = function () {
|
||||||
|
$scope.isLoadingBudget = true;
|
||||||
$http.get('/api/budgetapi/items', {
|
$http.get('/api/budgetapi/items', {
|
||||||
params: { month: $scope.selectedMonth, year: $scope.selectedYear }
|
params: { month: $scope.selectedMonth, year: $scope.selectedYear }
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
$scope.budgetItems = response.data;
|
$scope.budgetItems = response.data;
|
||||||
}).catch(error => console.error("Error fetching budget items:", error));
|
$scope.isLoadingBudget = false;
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Error fetching budget items:", error);
|
||||||
|
$scope.isLoadingBudget = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getItemsByCategory = function (category) {
|
$scope.getItemsByCategory = function (category) {
|
||||||
|
|||||||
@@ -1,18 +1,111 @@
|
|||||||
@{
|
@model Aberwyn.Models.WeeklyMenu
|
||||||
ViewData["Title"] = "Welcome to Lewel!";
|
@{
|
||||||
|
var showDate = (DateTime)ViewBag.ShowDate;
|
||||||
|
var day = showDate.ToString("dddd", new System.Globalization.CultureInfo("sv-SE"));
|
||||||
|
}
|
||||||
|
<link rel="stylesheet" href="~/css/Welcome.css" />
|
||||||
|
|
||||||
|
<section class="welcome-section light-mode">
|
||||||
|
<div class="welcome-content">
|
||||||
|
<h1 class="welcome-title">Välkommen till
|
||||||
|
<span class="initial L">L</span>
|
||||||
|
<span class="initial E">E</span>
|
||||||
|
<span class="initial W">W</span>
|
||||||
|
<span class="initial E2">E</span>
|
||||||
|
<span class="initial L2">L</span></h1>
|
||||||
|
<p class="welcome-subtitle">Meny för <strong>@day</strong> är:</p>
|
||||||
|
|
||||||
|
@if (Model != null)
|
||||||
|
{
|
||||||
|
<div class="meal-lines">
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) {
|
||||||
|
<div class="meal-line">
|
||||||
|
<p><strong>Frukost:</strong>
|
||||||
|
<a href="/meal/view/@Model.BreakfastMealId">@Model.BreakfastMealName</a>
|
||||||
|
</p>
|
||||||
|
@if (Model.BreakfastThumbnail != null)
|
||||||
|
{
|
||||||
|
var b64 = Convert.ToBase64String(Model.BreakfastThumbnail);
|
||||||
|
<div class="meal-large-thumb-container">
|
||||||
|
<img class="meal-large-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.BreakfastMealName" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
@if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) {
|
||||||
<html lang="en">
|
<div class="meal-line">
|
||||||
<head>
|
<p><strong>Lunch:</strong>
|
||||||
<meta charset="utf-8">
|
<a href="/meal/view/@Model.LunchMealId">@Model.LunchMealName</a>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
</p>
|
||||||
<title>@ViewData["Title"]</title>
|
@if (Model.LunchThumbnail != null)
|
||||||
|
{
|
||||||
</head>
|
var b64 = Convert.ToBase64String(Model.LunchThumbnail);
|
||||||
<body>
|
<div class="meal-large-thumb-container">
|
||||||
<div class="container">
|
<img class="meal-large-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.LunchMealName" />
|
||||||
<h1>Välkommen</h1> Kika på <a href="/Home/Menu" class="button">Menyn</a> om du vill.
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
}
|
||||||
</html>
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.DinnerMealName)) {
|
||||||
|
<div class="meal-line">
|
||||||
|
<p><strong>Middag:</strong>
|
||||||
|
<a href="/meal/view/@Model.DinnerMealId">@Model.DinnerMealName</a>
|
||||||
|
</p>
|
||||||
|
@if (Model.DinnerThumbnail != null)
|
||||||
|
{
|
||||||
|
var b64 = Convert.ToBase64String(Model.DinnerThumbnail);
|
||||||
|
<div class="meal-large-thumb-container">
|
||||||
|
<img class="meal-large-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.DinnerMealName" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@if (ViewBag.RestaurantIsOpen as bool? == true)
|
||||||
|
{
|
||||||
|
<p><strong>Pizzerian är öppen!</strong></p>
|
||||||
|
<a asp-controller="FoodMenu" asp-action="PizzaOrder">Klicka här för att Beställa pizza</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="no-menu">Ingen meny är inlagd för denna dag.</p>
|
||||||
|
}
|
||||||
|
<a class="nav-button" href="/Home/Menu">Visa hela veckomenyn</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div id="lightboxOverlay" class="lightbox-overlay" onclick="hideLargeImage()" style="display:none;">
|
||||||
|
<div class="lightbox-content" onclick="event.stopPropagation()">
|
||||||
|
<button class="close-lightbox" onclick="hideLargeImage()">×</button>
|
||||||
|
<img id="lightboxImage" class="lightbox-image" src="" alt="Meal image" />
|
||||||
|
<p id="lightboxCaption" class="lightbox-caption"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function showLargeImage(base64Data, caption) {
|
||||||
|
const overlay = document.getElementById("lightboxOverlay");
|
||||||
|
const image = document.getElementById("lightboxImage");
|
||||||
|
const text = document.getElementById("lightboxCaption");
|
||||||
|
image.src = `data:image/jpeg;base64,${base64Data}`;
|
||||||
|
text.textContent = caption || '';
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
document.body.classList.add("no-scroll");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLargeImage() {
|
||||||
|
document.getElementById("lightboxOverlay").style.display = "none";
|
||||||
|
document.body.classList.remove("no-scroll");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === "Escape") hideLargeImage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -32,13 +32,13 @@
|
|||||||
<div class="day-header">{{day}}</div>
|
<div class="day-header">{{day}}</div>
|
||||||
<div class="meal-info" ng-if="menu[day]">
|
<div class="meal-info" ng-if="menu[day]">
|
||||||
<div ng-if="menu[day].breakfastMealId" class="meal-selection">
|
<div ng-if="menu[day].breakfastMealId" class="meal-selection">
|
||||||
<a href="/Meal/View/{{menu[day].breakfastMealId}}" target="_blank"><strong>Frukost:</strong> {{menu[day].breakfastMealName}}</a>
|
<a href="/Meal/View/{{menu[day].breakfastMealId}}"><strong>Frukost:</strong> {{menu[day].breakfastMealName}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="menu[day].lunchMealId" class="meal-selection">
|
<div ng-if="menu[day].lunchMealId" class="meal-selection">
|
||||||
<a href="/Meal/View/{{menu[day].lunchMealId}}" target="_blank"><strong>Lunch:</strong> {{menu[day].lunchMealName}}</a>
|
<a href="/Meal/View/{{menu[day].lunchMealId}}"><strong>Lunch:</strong> {{menu[day].lunchMealName}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="menu[day].dinnerMealId" class="meal-selection">
|
<div ng-if="menu[day].dinnerMealId" class="meal-selection">
|
||||||
<a href="/Meal/View/{{menu[day].dinnerMealId}}" target="_blank"><strong>Middag:</strong> {{menu[day].dinnerMealName}}</a>
|
<a href="/Meal/View/{{menu[day].dinnerMealId}}"><strong>Middag:</strong> {{menu[day].dinnerMealName}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="!menu[day]">
|
<div ng-if="!menu[day]">
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
<div class="school-meals card-view">
|
<div class="school-meals card-view">
|
||||||
<h2>Skolmat</h2>
|
<h2>Skolmat</h2>
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@using Microsoft.AspNetCore.Http
|
||||||
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
|
|
||||||
|
@{
|
||||||
|
bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef");
|
||||||
|
}
|
||||||
|
|
||||||
<html lang="sv" ng-app="mealGalleryApp">
|
<html lang="sv" ng-app="mealGalleryApp">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -6,43 +13,77 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<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" href="/css/meal-gallery.css">
|
<link rel="stylesheet" href="/css/meal-gallery.css">
|
||||||
|
<script src="https://kit.fontawesome.com/yourkit.js" crossorigin="anonymous"></script>
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
body { overflow-x: hidden; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body ng-controller="MealGalleryController">
|
<body ng-controller="MealGalleryController">
|
||||||
<div class="meal-gallery-container" ng-controller="MealGalleryController">
|
<div class="meal-gallery-container">
|
||||||
<div class="meal-gallery-header">
|
<div class="meal-gallery-header">
|
||||||
<h1>Recept</h1>
|
<h1>Recept</h1>
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input type="text" ng-model="search" placeholder="Sök recept...">
|
<input type="text" ng-model="search" placeholder="Sök recept..." />
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="meal-gallery-grid">
|
@if (isChef)
|
||||||
<div class="meal-card" ng-repeat="meal in meals | filter:search">
|
{
|
||||||
<img ng-src="{{ meal.ThumbnailData ? 'data:image/webp;base64,' + meal.ThumbnailData : '/images/fallback.jpg' }}"
|
<label class="toggle-published">
|
||||||
alt="{{ meal.Name }}">
|
<input type="checkbox" ng-model="includeUnpublished" ng-change="reloadMeals()" />
|
||||||
<div class="meal-card-content">
|
<span>Visa alla</span>
|
||||||
<h3>{{ meal.Name }}</h3>
|
</label>
|
||||||
<p ng-if="meal.Description">{{ meal.Description }}</p>
|
|
||||||
<a class="btn-readmore" ng-href="/meal/view/{{ meal.Id }}">Läs mer</a>
|
<a href="/meal/view?edit=true" class="btn-create-meal">+ Ny rätt</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meal-gallery-grid">
|
||||||
|
<div class="meal-card"
|
||||||
|
ng-repeat="meal in (meals | filter:search) | limitTo:visibleCount track by meal.Id">
|
||||||
|
<img ng-src="{{ meal.ThumbnailData ? 'data:image/webp;base64,' + meal.ThumbnailData : '/images/fallback.jpg' }}"
|
||||||
|
alt="{{ meal.Name }}">
|
||||||
|
<div class="meal-card-content">
|
||||||
|
<h3>{{ meal.Name }}</h3>
|
||||||
|
<p ng-if="meal.Description">{{ meal.Description }}</p>
|
||||||
|
<a class="btn-readmore" ng-href="/meal/view/{{ meal.Id }}">Läs mer</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
angular.module("mealGalleryApp", []).controller("MealGalleryController", function ($scope, $http) {
|
angular.module("mealGalleryApp", []).controller("MealGalleryController", function ($scope, $http) {
|
||||||
$scope.meals = [];
|
$scope.meals = [];
|
||||||
$scope.search = "";
|
$scope.search = "";
|
||||||
|
$scope.visibleCount = 12;
|
||||||
|
$scope.includeUnpublished = false;
|
||||||
|
|
||||||
$http.get("/api/mealMenuApi/getMeals").then(res => {
|
$scope.reloadMeals = function () {
|
||||||
$scope.meals = res.data;
|
const url = `/api/mealMenuApi/getPublishedMeals?includeUnpublished=${$scope.includeUnpublished}`;
|
||||||
});
|
$http.get(url).then(res => {
|
||||||
|
$scope.meals = res.data;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.reloadMeals();
|
||||||
|
|
||||||
|
window.addEventListener('scroll', function () {
|
||||||
|
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 150) {
|
||||||
|
$scope.$applyAsync(() => {
|
||||||
|
$scope.visibleCount += 8;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watch('search', function (newVal) {
|
||||||
|
$scope.visibleCount = newVal ? 9999 : 12;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
173
Aberwyn/Views/Meal/Lab.cshtml
Normal file
173
Aberwyn/Views/Meal/Lab.cshtml
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
@model Aberwyn.Models.RecipeLabEntry
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Matlabb";
|
||||||
|
}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/css/lab.css" />
|
||||||
|
|
||||||
|
<div class="lab-container">
|
||||||
|
<!-- Vänster: formulär för labb-entry -->
|
||||||
|
<div class="lab-main">
|
||||||
|
<h2>Matlabb – @(string.IsNullOrEmpty(Model.Title) ? "Ny" : Model.Title)</h2>
|
||||||
|
|
||||||
|
<form asp-action="SaveLabEntry" method="post">
|
||||||
|
<input type="hidden" name="Id" value="@Model.Id" />
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Titel</label>
|
||||||
|
<input type="text" name="Title" value="@Model.Title" class="lab-input" required />
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Inspiration</label>
|
||||||
|
<input type="text" name="Inspiration" value="@Model.Inspiration" class="lab-input" />
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Anteckningar</label>
|
||||||
|
<textarea name="Notes" class="lab-textarea">@Model.Notes</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Taggar</label>
|
||||||
|
<input type="text" name="Tags" value="@Model.Tags" class="lab-input" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="lab-save-button">Spara labb</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@if (Model.Id > 0)
|
||||||
|
{
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Ingredienser sektion -->
|
||||||
|
<h3>Ingredienser</h3>
|
||||||
|
<div id="ingredients-section">
|
||||||
|
<form asp-action="SaveLabIngredients" method="post">
|
||||||
|
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
||||||
|
<div id="ingredients-list">
|
||||||
|
@if (Model.Ingredients?.Any() == true)
|
||||||
|
{
|
||||||
|
@for (int i = 0; i < Model.Ingredients.Count; i++)
|
||||||
|
{
|
||||||
|
<div class="ingredient-row">
|
||||||
|
<input type="text" name="Ingredients[@i].Quantity" value="@Model.Ingredients[i].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
|
<input type="text" name="Ingredients[@i].Item" value="@Model.Ingredients[i].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="ingredient-row">
|
||||||
|
<input type="text" name="Ingredients[0].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
|
<input type="text" name="Ingredients[0].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="addIngredient()" class="lab-add-button">Lägg till ingrediens</button>
|
||||||
|
<button type="submit" class="lab-save-button">Spara ingredienser</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Ny version sektion -->
|
||||||
|
<h3>Ny version</h3>
|
||||||
|
<form asp-action="AddLabVersion" method="post">
|
||||||
|
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Version-label</label>
|
||||||
|
<input type="text" name="VersionLabel" value="v@((Model.Versions?.Count + 1 ?? 1))" class="lab-input" />
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Instruktioner</label>
|
||||||
|
<textarea name="Instructions" class="lab-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Resultat</label>
|
||||||
|
<textarea name="ResultNotes" class="lab-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="lab-add-button">Lägg till version</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p><em>Spara labbet först för att kunna lägga till ingredienser och versioner.</em></p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Höger: versioner -->
|
||||||
|
<div class="lab-versions">
|
||||||
|
<h3>Versioner</h3>
|
||||||
|
@if (Model.Versions?.Any() == true)
|
||||||
|
{
|
||||||
|
<div class="version-scroll">
|
||||||
|
@foreach (var version in Model.Versions.OrderByDescending(v => v.CreatedAt))
|
||||||
|
{
|
||||||
|
<div class="version-card">
|
||||||
|
<h4>@version.VersionLabel</h4>
|
||||||
|
<p><strong>Skapat:</strong> @version.CreatedAt.ToString("yyyy-MM-dd")</p>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(version.Instructions))
|
||||||
|
{
|
||||||
|
<p><strong>Instruktioner:</strong><br />@version.Instructions</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(version.ResultNotes))
|
||||||
|
{
|
||||||
|
<p><strong>Resultat:</strong><br />@version.ResultNotes</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (version.Ingredients?.Any() == true)
|
||||||
|
{
|
||||||
|
<p><strong>Ingredienser:</strong></p>
|
||||||
|
<ul>
|
||||||
|
@foreach (var ing in version.Ingredients)
|
||||||
|
{
|
||||||
|
<li>@ing.Quantity @ing.Item</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Inga tidigare versioner ännu.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ingredientIndex = @(Model.Ingredients?.Count ?? 1);
|
||||||
|
|
||||||
|
function addIngredient() {
|
||||||
|
const list = document.getElementById('ingredients-list');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'ingredient-row';
|
||||||
|
div.innerHTML = `
|
||||||
|
<input type="text" name="Ingredients[${ingredientIndex}].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
|
<input type="text" name="Ingredients[${ingredientIndex}].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
|
`;
|
||||||
|
list.appendChild(div);
|
||||||
|
ingredientIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeIngredient(button) {
|
||||||
|
const row = button.closest('.ingredient-row');
|
||||||
|
row.remove();
|
||||||
|
reindexIngredients();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reindexIngredients() {
|
||||||
|
const rows = document.querySelectorAll('.ingredient-row');
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
const quantityInput = row.querySelector('input[name*="Quantity"]');
|
||||||
|
const itemInput = row.querySelector('input[name*="Item"]');
|
||||||
|
|
||||||
|
if (quantityInput) quantityInput.name = `Ingredients[${index}].Quantity`;
|
||||||
|
if (itemInput) itemInput.name = `Ingredients[${index}].Item`;
|
||||||
|
});
|
||||||
|
ingredientIndex = rows.length;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
74
Aberwyn/Views/Meal/MealCategories.cshtml
Normal file
74
Aberwyn/Views/Meal/MealCategories.cshtml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
@model List<Aberwyn.Models.MealCategory>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Måltidskategorier";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="page-title">Måltidskategorier</h1>
|
||||||
|
|
||||||
|
<div class="category-list">
|
||||||
|
@foreach (var cat in Model)
|
||||||
|
{
|
||||||
|
<form method="post" action="/meal/categories/save" class="category-row">
|
||||||
|
<input type="hidden" name="Id" value="@cat.Id" />
|
||||||
|
|
||||||
|
<div class="name-with-preview">
|
||||||
|
<div class="icon-preview" style="background-color: @cat.Color">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(cat.Icon))
|
||||||
|
{
|
||||||
|
<span>@cat.Icon</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>🍽️</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<input name="Name" value="@cat.Name" placeholder="Namn" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input name="Icon" value="@cat.Icon" placeholder="Ikon (emoji eller klass)" />
|
||||||
|
<input name="Slug" value="@cat.Slug" placeholder="Slug" />
|
||||||
|
<input name="Color" value="@cat.Color" type="color" title="Färg" />
|
||||||
|
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="IsActive" @(cat.IsActive ? "checked" : "") />
|
||||||
|
Aktiv
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="meal-count">@cat.MealCount st</span>
|
||||||
|
|
||||||
|
<div class="row-actions">
|
||||||
|
<button type="submit" title="Spara"><i class="fas fa-save"></i></button>
|
||||||
|
<form method="post" action="/meal/categories/delete" style="display:inline;">
|
||||||
|
<input type="hidden" name="id" value="@cat.Id" />
|
||||||
|
<button type="submit" class="delete" title="Ta bort"><i class="fas fa-trash"></i></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Ny kategori -->
|
||||||
|
<form method="post" action="/meal/categories/save" class="category-row new">
|
||||||
|
<div class="name-with-preview">
|
||||||
|
<div class="icon-preview" style="background-color: #6a0dad">➕</div>
|
||||||
|
<input name="Name" placeholder="Ny kategori" autofocus />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input name="Icon" placeholder="Ikon" />
|
||||||
|
<input name="Slug" placeholder="Slug" />
|
||||||
|
<input name="Color" type="color" value="#6a0dad" />
|
||||||
|
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="IsActive" checked />
|
||||||
|
Aktiv
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span></span>
|
||||||
|
|
||||||
|
<div class="row-actions">
|
||||||
|
<button type="submit" title="Lägg till"><i class="fas fa-plus"></i></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="/css/meal-categories.css">
|
||||||
@@ -14,12 +14,13 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
imageSrc = "/images/placeholder-meal.jpg";
|
imageSrc = "/images/fallback.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef");
|
bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef");
|
||||||
isEditing = isEditing && isChef;
|
isEditing = isEditing && isChef;
|
||||||
}
|
}
|
||||||
|
<link rel="stylesheet" href="/css/meal.css">
|
||||||
|
|
||||||
<div class="meal-container">
|
<div class="meal-container">
|
||||||
<div class="meal-header">
|
<div class="meal-header">
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
@if (isEditing)
|
@if (isEditing)
|
||||||
{
|
{
|
||||||
<div style="margin-top: 0.5rem;">
|
<div style="margin-top: 0.5rem;">
|
||||||
<button type="button" class="btn-outline" onclick="openImageModal()">Byt bild</button>
|
<button type="button" class="btn-lewel-outline" onclick="openImageModal()">Byt bild</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
<h1 class="meal-title">@Model.Name</h1>
|
<h1 class="meal-title">@Model.Name</h1>
|
||||||
<p class="description">@Model.Description</p>
|
<p class="description">@Model.Description</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (isEditing)
|
@if (isEditing)
|
||||||
@@ -64,11 +66,16 @@
|
|||||||
<label for="CarbType">Kolhydrat</label>
|
<label for="CarbType">Kolhydrat</label>
|
||||||
<input type="text" name="CarbType" value="@Model.CarbType" class="form-control" />
|
<input type="text" name="CarbType" value="@Model.CarbType" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="Category">Kategori</label>
|
<label for="MealCategoryId">Kategori</label>
|
||||||
<input type="text" name="Category" value="@Model.Category" class="form-control" />
|
<select asp-for="MealCategoryId" asp-items="@(new SelectList((List<MealCategory>)ViewBag.Categories, "Id", "Name", Model.MealCategoryId))" class="form-control">
|
||||||
|
<option value="">-- Välj kategori --</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="RecipeUrl">Receptlänk</label>
|
<label for="RecipeUrl">Receptlänk</label>
|
||||||
<input type="url" name="RecipeUrl" value="@Model.RecipeUrl" class="form-control" />
|
<input type="url" name="RecipeUrl" value="@Model.RecipeUrl" class="form-control" />
|
||||||
@@ -91,14 +98,26 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-outline" onclick="addIngredientRow()">+ Lägg till ingrediens</button>
|
<div style="margin-top: 0.5rem;">
|
||||||
</div>
|
<label for="bulkIngredients">Klistra in flera ingredienser</label>
|
||||||
</div>
|
<textarea id="bulkIngredients" placeholder="1 dl mjölk 2 tsk socker" class="form-control" rows="3"></textarea>
|
||||||
|
<button type="button" class="btn-lewel-outline" onclick="parseBulkIngredients()">Lägg till från lista</button>
|
||||||
|
<button type="button" class="btn-lewel-outline" onclick="addIngredientRow()">+ Lägg till rad</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="IsPublished" value="true" @(Model.IsPublished ? "checked" : "") />
|
||||||
|
Publicera recept på receptsidan
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="btn">Spara</button>
|
<button type="submit" class="btn-lewel">Spara</button>
|
||||||
<button type="submit" formaction="@Url.Action("DeleteMeal", new { id = Model.Id })" formmethod="post" onclick="return confirm('Vill du verkligen ta bort denna måltid?');" class="btn-outline">Ta bort</button>
|
<button type="submit" formaction="@Url.Action("DeleteMeal", new { id = Model.Id })" formmethod="post" onclick="return confirm('Vill du verkligen ta bort denna måltid?');" class="btn-lewel-outline">Ta bort</button>
|
||||||
<a href="@Url.Action("View", new { id = Model.Id, edit = false })" class="btn-outline">Avbryt</a>
|
<a href="@Url.Action("View", new { id = Model.Id, edit = false })" class="btn-lewel-outline">Avbryt</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@@ -119,15 +138,29 @@
|
|||||||
{
|
{
|
||||||
<p><a href="@Model.RecipeUrl" class="recipe-link" target="_blank">Visa Recept</a></p>
|
<p><a href="@Model.RecipeUrl" class="recipe-link" target="_blank">Visa Recept</a></p>
|
||||||
}
|
}
|
||||||
|
@if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
<div class="rating-box" data-meal-id="@Model.Id">
|
||||||
|
<p>Ditt betyg:</p>
|
||||||
|
<div class="star-container">
|
||||||
|
@for (int i = 1; i <= 5; i++)
|
||||||
|
{
|
||||||
|
<i class="fa fa-star" data-value="@i"></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="buttons">
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
@if (isChef)
|
@if (isChef)
|
||||||
{
|
{
|
||||||
<a class="btn-outline" href="@Url.Action("View", new { id = Model.Id, edit = true })">Redigera</a>
|
<a class="btn-lewel-outline slim" href="@Url.Action("View", new { id = Model.Id, edit = true })">Redigera</a>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn-outline" onclick="toggleRecipe()">Recept</button>
|
<button type="button" class="btn-lewel-outline slim" onclick="toggleRecipe()">Recept</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="recipe-section" style="display:none; margin-top:1.5rem;">
|
<div id="recipe-section" style="display:none; margin-top:1.5rem;">
|
||||||
<h2>Så här gör du</h2>
|
<h2>Så här gör du</h2>
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.Instructions))
|
@if (!string.IsNullOrWhiteSpace(Model.Instructions))
|
||||||
@@ -169,8 +202,95 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trumbowyg@2.25.1/dist/ui/trumbowyg.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trumbowyg@2.25.1/dist/ui/trumbowyg.min.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/trumbowyg@2.25.1/dist/trumbowyg.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/trumbowyg@2.25.1/dist/trumbowyg.min.js"></script>
|
||||||
|
<script src="https://kit.fontawesome.com/yourkit.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const ratingBox = document.querySelector(".rating-box");
|
||||||
|
if (!ratingBox) return;
|
||||||
|
|
||||||
|
const mealId = ratingBox.dataset.mealId;
|
||||||
|
const stars = ratingBox.querySelectorAll(".fa-star");
|
||||||
|
|
||||||
|
fetch(`/api/MealRatingApi/${mealId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(rating => {
|
||||||
|
stars.forEach(star => {
|
||||||
|
if (parseInt(star.dataset.value) <= rating) {
|
||||||
|
star.classList.add("rated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
stars.forEach(star => {
|
||||||
|
star.addEventListener("click", () => {
|
||||||
|
const rating = star.dataset.value;
|
||||||
|
fetch("/api/MealRatingApi", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ mealId: mealId, rating: parseInt(rating) })
|
||||||
|
}).then(() => {
|
||||||
|
stars.forEach(s => s.classList.remove("rated"));
|
||||||
|
stars.forEach(s => {
|
||||||
|
if (parseInt(s.dataset.value) <= rating) {
|
||||||
|
s.classList.add("rated");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function addIngredientRow(quantity = '', item = '') {
|
||||||
|
const list = document.getElementById('ingredients-list');
|
||||||
|
const index = list.children.length;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'ingredient-row';
|
||||||
|
|
||||||
|
const qtyInput = document.createElement('input');
|
||||||
|
qtyInput.type = 'text';
|
||||||
|
qtyInput.name = `Ingredients[${index}].Quantity`;
|
||||||
|
qtyInput.placeholder = 'Mängd';
|
||||||
|
qtyInput.className = 'form-control ingredient-qty';
|
||||||
|
qtyInput.value = quantity;
|
||||||
|
|
||||||
|
const itemInput = document.createElement('input');
|
||||||
|
itemInput.type = 'text';
|
||||||
|
itemInput.name = `Ingredients[${index}].Item`;
|
||||||
|
itemInput.placeholder = 'Ingrediens';
|
||||||
|
itemInput.className = 'form-control ingredient-item';
|
||||||
|
itemInput.value = item;
|
||||||
|
|
||||||
|
div.appendChild(qtyInput);
|
||||||
|
div.appendChild(itemInput);
|
||||||
|
list.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBulkIngredients() {
|
||||||
|
const bulk = document.getElementById('bulkIngredients').value;
|
||||||
|
if (!bulk.trim()) return;
|
||||||
|
|
||||||
|
const lines = bulk.trim().split('\n');
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) continue;
|
||||||
|
|
||||||
|
// Försök dela på första mellanslag → mängd + ingrediens
|
||||||
|
const parts = trimmed.split(' ');
|
||||||
|
const quantity = parts.slice(0, 2).join(' '); // typ "2 dl" eller "1 tsk"
|
||||||
|
const item = parts.slice(2).join(' '); // resten
|
||||||
|
|
||||||
|
// fallback om bara ett ord: "ägg"
|
||||||
|
const safeQuantity = item ? quantity : '';
|
||||||
|
const safeItem = item || quantity;
|
||||||
|
|
||||||
|
addIngredientRow(safeQuantity, safeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('bulkIngredients').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function toggleRecipe() {
|
function toggleRecipe() {
|
||||||
const section = document.getElementById('recipe-section');
|
const section = document.getElementById('recipe-section');
|
||||||
if (!section) return;
|
if (!section) return;
|
||||||
@@ -231,166 +351,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dragging {
|
|
||||||
outline: 2px dashed #6a0dad;
|
|
||||||
}
|
|
||||||
.meal-image-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.meal-container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 2rem auto;
|
|
||||||
background: #fff;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
||||||
font-family: 'Segoe UI', sans-serif;
|
|
||||||
}
|
|
||||||
.meal-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 1.5rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.meal-meta {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.meal-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.meal-image {
|
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #555;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
.meal-details p {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #333;
|
|
||||||
margin: 0.3rem 0;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #6a0dad;
|
|
||||||
}
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.form-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.3rem;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
.btn,
|
|
||||||
.btn-outline {
|
|
||||||
background-color: #6a0dad;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.6rem 1.4rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 0.4rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.btn-outline {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 2px solid #6a0dad;
|
|
||||||
color: #6a0dad;
|
|
||||||
}
|
|
||||||
.recipe-link {
|
|
||||||
color: #6a0dad;
|
|
||||||
text-decoration: underline;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.placeholder {
|
|
||||||
color: #999;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.ingredient-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-qty {
|
|
||||||
flex: 1 0 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-item {
|
|
||||||
flex: 2 0 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@media (max-width: 768px) {
|
|
||||||
.meal-container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meal-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meal-meta {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meal-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meal-image {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-row {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-qty,
|
|
||||||
.ingredient-item {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn,
|
|
||||||
.btn-outline {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
43
Aberwyn/Views/Movie/Search.cshtml
Normal file
43
Aberwyn/Views/Movie/Search.cshtml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Filmsök";
|
||||||
|
}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>🎬 Filmsök</h2>
|
||||||
|
<input type="text" class="form-control mb-3" id="searchInput" placeholder="Sök efter en film..." oninput="searchMovie()" />
|
||||||
|
|
||||||
|
<div class="results row" id="results"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
const API_KEY = 'aef2f49296b77b9b9c269678d04bdbc6';
|
||||||
|
const BASE_URL = 'https://api.themoviedb.org/3';
|
||||||
|
const IMG_BASE = 'https://image.tmdb.org/t/p/w300';
|
||||||
|
|
||||||
|
async function searchMovie() {
|
||||||
|
const query = document.getElementById('searchInput').value.trim();
|
||||||
|
const resultContainer = document.getElementById('results');
|
||||||
|
resultContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (query.length < 2) return;
|
||||||
|
|
||||||
|
const res = await fetch(`${BASE_URL}/search/movie?api_key=${API_KEY}&language=sv-SE&query=${encodeURIComponent(query)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
data.results.forEach(movie => {
|
||||||
|
const col = document.createElement('div');
|
||||||
|
col.className = 'col-md-3 mb-4';
|
||||||
|
col.innerHTML = `
|
||||||
|
<div class="card h-100 bg-dark text-white border-0 shadow">
|
||||||
|
<img src="${movie.poster_path ? IMG_BASE + movie.poster_path : 'https://via.placeholder.com/300x450?text=Ingen+bild'}" class="card-img-top" alt="${movie.title}" />
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">${movie.title}</h5>
|
||||||
|
<p class="card-text"><small>${movie.release_date || 'Okänt datum'}</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultContainer.appendChild(col);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest-v2.json">
|
||||||
<link rel="icon" type="image/png" sizes="512x512" href="/images/lewel-icon.png">
|
<link rel="icon" type="image/png" sizes="512x512" href="/images/lewel-icon.png">
|
||||||
<meta name="theme-color" content="#6a0dad">
|
<meta name="theme-color" content="#6a0dad">
|
||||||
|
<style>
|
||||||
|
[ng-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<meta name="theme-color" content="#1F2C3C" />
|
||||||
<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">
|
||||||
<title>LEWEL - Dashboard</title>
|
<title>LEWEL - Dashboard</title>
|
||||||
@@ -28,49 +34,59 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<aside class="sidebar">
|
<nav class="main-nav">
|
||||||
@if (ViewBag.IsSetupMode as bool? != true)
|
<ul class="nav-list-horizontal">
|
||||||
{
|
<li><a asp-controller="Home" asp-action="Index"><i class="fas fa-home"></i> Hem</a></li>
|
||||||
<ul class="nav-list">
|
<li><a asp-controller="Home" asp-action="Menu"><i class="fas fa-utensils"></i> Veckomeny</a></li>
|
||||||
<li><a asp-controller="Home" asp-action="Index"><i class="fas fa-home"></i> Home</a></li>
|
<li><a asp-controller="Meal" asp-action="Index">Recept</a></li>
|
||||||
@if (User.IsInRole("Budget"))
|
|
||||||
{
|
|
||||||
<li><a asp-controller="Budget" asp-action="Index"><i class="fas fa-wallet"></i> Budget</a></li>
|
|
||||||
}
|
|
||||||
<li><a asp-controller="Home" asp-action="Menu"><i class="fas fa-utensils"></i> Veckomeny</a></li>
|
|
||||||
@if (ViewBag.RestaurantIsOpen as bool? == true)
|
|
||||||
{
|
|
||||||
<li><a asp-controller="FoodMenu" asp-action="PizzaOrder"><i class="fas fa-pizza-slice"></i> Beställ pizza</a></li>
|
|
||||||
}
|
|
||||||
@if (User.IsInRole("Chef"))
|
|
||||||
{
|
|
||||||
<li><a asp-controller="FoodMenu" asp-action="Veckomeny"><i class="fas fa-calendar-week"></i> Administrera Veckomeny</a></li>
|
|
||||||
<li><a asp-controller="FoodMenu" asp-action="MealAdmin"><i class="fas fa-list"></i> Måltider</a></li>
|
|
||||||
<li><a asp-controller="FoodMenu" asp-action="PizzaAdmin"><i class="fas fa-list"></i> Pizza Admin</a></li>
|
|
||||||
|
|
||||||
}
|
@if (ViewBag.RestaurantIsOpen as bool? == true)
|
||||||
|
{
|
||||||
|
<li><a asp-controller="FoodMenu" asp-action="PizzaOrder"><i class="fas fa-pizza-slice"></i> Beställ pizza</a></li>
|
||||||
|
}
|
||||||
|
@if (User.IsInRole("Budget"))
|
||||||
|
{
|
||||||
|
<li><a asp-controller="Budget" asp-action="Index"> Budget</a></li>
|
||||||
|
}
|
||||||
|
@if (User.IsInRole("Chef"))
|
||||||
|
{
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" role="button" tabindex="0">
|
||||||
|
<i class="fas fa-list"></i> Mat<i class="fas fa-caret-down"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a asp-controller="FoodMenu" asp-action="Veckomeny">Planera Veckomeny</a></li>
|
||||||
|
<li><a asp-controller="FoodMenu" asp-action="PizzaAdmin">Pizza Admin</a></li>
|
||||||
|
<li><a asp-controller="Meal" asp-action="Categories">Hantera Kategorier</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
@if (User.IsInRole("Admin"))
|
@if (User.IsInRole("Admin"))
|
||||||
{
|
{
|
||||||
<li><a asp-controller="Admin" asp-action="Index"><i class="fas fa-cog"></i> Adminpanel</a></li>
|
<li class="dropdown">
|
||||||
<li><a asp-controller="Admin" asp-action="Todo"><i class="fas fa-cog"></i> Todo</a></li>
|
<a href="#" class="dropdown-toggle" role="button" tabindex="0">
|
||||||
|
<i class="fas fa-cog"></i> Admin <i class="fas fa-caret-down"></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a asp-controller="Admin" asp-action="Index">Adminpanel</a></li>
|
||||||
|
<li><a asp-controller="Admin" asp-action="Todo">Todo</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
<ul class="nav-list">
|
|
||||||
<li><a asp-controller="Setup" asp-action="Index"> Setup</a></li>
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="main-panel">
|
<main class="main-panel">
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
@RenderSection("Scripts", required: false)
|
@RenderSection("Scripts", required: false)
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="global-dropdown-container" style="position: relative; z-index: 10000;"></div>
|
||||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
@model Aberwyn.Models.UserProfileViewModel
|
@model Aberwyn.Models.UserProfileViewModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Userprofile";
|
ViewData["Title"] = "Min profil";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2>Min profil</h2>
|
<h2>Min profil</h2>
|
||||||
|
|
||||||
<form method="post" asp-action="SaveProfile">
|
<form method="post" asp-action="SaveProfile">
|
||||||
<div>
|
<div>
|
||||||
<label asp-for="Name"></label>
|
<label asp-for="Name"></label>
|
||||||
@@ -29,4 +30,108 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit">Spara</button>
|
<button type="submit">Spara</button>
|
||||||
|
|
||||||
|
<div style="margin-top: 1em;">
|
||||||
|
<div id="notificationStatus" style="font-weight: bold;"></div>
|
||||||
|
<button type="button" id="enableNotificationsBtn">Aktivera notiser</button>
|
||||||
|
<button type="button" id="disableNotificationsBtn">Stäng av notiser</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
async function updateNotificationStatus() {
|
||||||
|
const el = document.getElementById("notificationStatus");
|
||||||
|
|
||||||
|
if (!("Notification" in window)) {
|
||||||
|
el.textContent = "⛔️ Pushnotiser stöds inte i denna webbläsare.";
|
||||||
|
el.style.color = "gray";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = Notification.permission;
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const existing = await reg.pushManager.getSubscription();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case "granted":
|
||||||
|
el.textContent = existing ? "✅ Pushnotiser är aktiverade." : "🟡 Tillåtet men ingen prenumeration aktiv.";
|
||||||
|
el.style.color = existing ? "green" : "orange";
|
||||||
|
break;
|
||||||
|
case "denied":
|
||||||
|
el.textContent = "❌ Pushnotiser är blockerade.";
|
||||||
|
el.style.color = "red";
|
||||||
|
break;
|
||||||
|
case "default":
|
||||||
|
el.textContent = "❔ Pushnotiser har inte begärts ännu.";
|
||||||
|
el.style.color = "orange";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", updateNotificationStatus);
|
||||||
|
|
||||||
|
document.getElementById("enableNotificationsBtn").addEventListener("click", async () => {
|
||||||
|
if (!("Notification" in window)) {
|
||||||
|
alert("Notiser stöds inte i denna webbläsare.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
if (permission !== "granted") {
|
||||||
|
alert("Du måste tillåta notiser.");
|
||||||
|
updateNotificationStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const existing = await reg.pushManager.getSubscription();
|
||||||
|
if (existing) {
|
||||||
|
alert("🔔 Du är redan prenumererad på notiser.");
|
||||||
|
updateNotificationStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vapidPublicKey = await fetch('/api/push/vapid-public-key').then(r => r.text());
|
||||||
|
const sub = await reg.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey.trim())
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetch("/api/push/subscribe-user", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(sub),
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
});
|
||||||
|
|
||||||
|
alert("✅ Du är nu prenumererad!");
|
||||||
|
updateNotificationStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("disableNotificationsBtn").addEventListener("click", async () => {
|
||||||
|
const reg = await navigator.serviceWorker.ready;
|
||||||
|
const sub = await reg.pushManager.getSubscription();
|
||||||
|
if (!sub) {
|
||||||
|
alert("ℹ️ Ingen prenumeration att ta bort.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sub.unsubscribe();
|
||||||
|
await fetch("/api/push/unsubscribe", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ endpoint: sub.endpoint }),
|
||||||
|
headers: { "Content-Type": "application/json" }
|
||||||
|
});
|
||||||
|
|
||||||
|
alert("🚫 Prenumeration avslutad.");
|
||||||
|
updateNotificationStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
function urlBase64ToUint8Array(base64String) {
|
||||||
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
|
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const raw = atob(base64);
|
||||||
|
return new Uint8Array([...raw].map(char => char.charCodeAt(0)));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|||||||
194
Aberwyn/wwwroot/css/Welcome.css
Normal file
194
Aberwyn/wwwroot/css/Welcome.css
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
.welcome-section.light-mode {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 90vh;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: linear-gradient(to bottom right, #f9fafb, #e2e8f0);
|
||||||
|
animation: fadeIn 0.6s ease-in;
|
||||||
|
}
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-content {
|
||||||
|
max-width: 700px;
|
||||||
|
color: #1e293b;
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: #eab308;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-line {
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-line p {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-line a {
|
||||||
|
color: #007d36;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-line a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-large-thumb-container {
|
||||||
|
margin-top: 0rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
height: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-large-thumb {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
height: 160px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.no-menu {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 12px 26px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 24px;
|
||||||
|
transition: background 0.2s ease, transform 0.2s ease;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.thumb-button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-thumb {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-content {
|
||||||
|
position: relative;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-caption {
|
||||||
|
color: white;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-lightbox {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: -10px;
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 20px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
:root {
|
:root {
|
||||||
--text-main: #1E293B;
|
--text-main: #1F2937; /* Mörkblågrå – tydlig men mjuk */
|
||||||
--text-sub: #64748B;
|
--text-sub: #64748B; /* Sekundär gråblå */
|
||||||
--bg-main: #f9fafb;
|
--bg-main: #1F2C3C; /* SID-bakgrund */
|
||||||
--bg-card: #f1f5f9;
|
--bg-card: #dbe3ec;
|
||||||
--border-color: #e5e7eb;
|
--bg-card-summary: #d6dde6;
|
||||||
--card-income: #f97316;
|
--border-color: #cbd5e1; /* Ljus kant men inte skarp */
|
||||||
|
--item-divider: rgba(0, 0, 0, 0.05); /* ljus standard */
|
||||||
|
|
||||||
|
--card-income: #fb923c;
|
||||||
--card-expense: #ef4444;
|
--card-expense: #ef4444;
|
||||||
--card-savings: #facc15;
|
--card-savings: #facc15;
|
||||||
--card-leftover: #86efac;
|
--card-leftover: #4ade80;
|
||||||
--btn-edit: #3b82f6;
|
--btn-edit: #3b82f6;
|
||||||
--btn-check: #10b981;
|
--btn-check: #10b981;
|
||||||
--btn-delete: #ef4444;
|
--btn-delete: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--bg-main);
|
background-color: var(--bg-main);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
@@ -105,6 +110,7 @@ body {
|
|||||||
height: 350px;
|
height: 350px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
color: var(--text-main);
|
||||||
background-color: var(--bg-card);
|
background-color: var(--bg-card);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -134,6 +140,7 @@ body {
|
|||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
border-radius: 0 0 12px 12px;
|
border-radius: 0 0 12px 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
background: var(--bg-card-summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
@@ -235,7 +242,35 @@ body {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
@keyframes fadeRed {
|
||||||
|
0% {
|
||||||
|
color: rgb(200, 50, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
color: rgb(160, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeGreen {
|
||||||
|
0% {
|
||||||
|
color: rgb(50, 200, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
color: rgb(0, 160, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-row.due {
|
||||||
|
animation: fadeRed 0.2s ease-in-out;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-row.paid {
|
||||||
|
animation: fadeGreen 0.2s ease-in-out;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
@@ -261,11 +296,17 @@ color: var(--btn-edit);
|
|||||||
.icon-button.confirm {
|
.icon-button.confirm {
|
||||||
color: var(--btn-check);
|
color: var(--btn-check);
|
||||||
}
|
}
|
||||||
|
.menu-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
|
||||||
top: calc(100% + 6px);
|
top: calc(100% + 6px);
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(-100%) translateX(100%); /* Effektivt: ingen förskjutning */
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -273,27 +314,29 @@ color: var(--btn-check);
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
display: none;
|
display: none;
|
||||||
gap: 4px;
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
min-width: 160px;
|
||||||
min-width: 200px;
|
max-width: 240px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.menu-container.open .dropdown-menu {
|
.menu-container.open .dropdown-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu button {
|
.dropdown-menu button {
|
||||||
padding: 5px 8px;
|
padding: 6px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #ddd;
|
border: none;
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
flex: 1;
|
text-align: left;
|
||||||
font-weight: 500;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu button:hover {
|
.dropdown-menu button:hover {
|
||||||
background-color: #e5e7eb;
|
background-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +564,10 @@ color: var(--btn-check);
|
|||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
|
background-color: var(--bg-card);
|
||||||
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.budget-summary-box h3,
|
.budget-summary-box h3,
|
||||||
@@ -601,7 +647,18 @@ canvas {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body.dark-mode {
|
||||||
|
--item-divider: rgba(255, 255, 255, 0.05); /* för mörk bakgrund */
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-row:not(.total-row) {
|
||||||
|
border-bottom: 1px solid var(--item-divider);
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
|
|
||||||
.budget-overview-row {
|
.budget-overview-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -634,10 +691,51 @@ canvas {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
.item-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.item-label {
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
}
|
||||||
.budget-overview-row {
|
.budget-overview-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -662,6 +760,8 @@ canvas {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
color: #1E293B; /* djupare textfärg */
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-area canvas {
|
.chart-area canvas {
|
||||||
@@ -669,3 +769,13 @@ canvas {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.loading-indicator {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 40px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #64748B;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
[ng-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
116
Aberwyn/wwwroot/css/lab.css
Normal file
116
Aberwyn/wwwroot/css/lab.css
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
.lab-flex-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section {
|
||||||
|
background: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #1F2C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.ingredients {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.versions {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-field {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-field input,
|
||||||
|
.lab-field textarea {
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-version-form {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-version {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-notes {
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-result {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #6a0dad;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #6a0dad;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 12px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #5a0ca0;
|
||||||
|
}
|
||||||
110
Aberwyn/wwwroot/css/meal-categories.css
Normal file
110
Aberwyn/wwwroot/css/meal-categories.css
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
.page-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2.5fr 1.2fr 1.2fr 1fr auto auto auto;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background-color: #1F2C3C;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-row.new {
|
||||||
|
background-color: #2E3C4F;
|
||||||
|
border: 2px dashed #6a0dad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-row input[type="text"],
|
||||||
|
.category-row input[type="color"],
|
||||||
|
.category-row input:not([type="checkbox"]) {
|
||||||
|
padding: 0.4rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #334155;
|
||||||
|
color: #fefefe;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #ddd;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions button {
|
||||||
|
background-color: #6a0dad;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions button.delete {
|
||||||
|
background-color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions i {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-with-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-preview {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #6a0dad;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: white;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 2px solid #ffffff22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-count {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.category-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-count {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,105 +3,211 @@ body {
|
|||||||
font-family: 'Segoe UI', sans-serif;
|
font-family: 'Segoe UI', sans-serif;
|
||||||
color: #222;
|
color: #222;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-gallery-container {
|
.meal-gallery-container {
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
}
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
.meal-gallery-header {
|
/* === Header och sökfält === */
|
||||||
|
.meal-gallery-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-gallery-header h1 {
|
.meal-gallery-header h1 {
|
||||||
font-size: 2.4rem;
|
font-size: 2.2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.search-container {
|
/* Sökfält + checkbox */
|
||||||
|
.search-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 400px;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-container input {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 38px 10px 12px;
|
max-width: 600px;
|
||||||
border-radius: 25px;
|
margin-inline: auto;
|
||||||
border: 1px solid #ccc;
|
display: flex;
|
||||||
font-size: 1rem;
|
align-items: center;
|
||||||
}
|
gap: 1rem;
|
||||||
|
background: #e9eef3;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
.search-container i {
|
.search-container input[type="text"] {
|
||||||
position: absolute;
|
flex-grow: 1;
|
||||||
right: 12px;
|
padding: 8px 12px;
|
||||||
top: 50%;
|
border-radius: 8px;
|
||||||
transform: translateY(-50%);
|
border: 1px solid #ccc;
|
||||||
color: #888;
|
font-size: 1rem;
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.meal-gallery-grid {
|
.search-container i {
|
||||||
|
color: #888;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkboxdel */
|
||||||
|
.toggle-published {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #222;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-published input[type="checkbox"] {
|
||||||
|
accent-color: #3399ff;
|
||||||
|
transform: scale(1.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Grid layout === */
|
||||||
|
.meal-gallery-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
gap: 24px;
|
gap: 20px;
|
||||||
}
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.meal-card {
|
/* === Kortstruktur === */
|
||||||
|
.meal-card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-card:hover {
|
.meal-card:hover {
|
||||||
transform: scale(1.015);
|
transform: scale(1.015);
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-card img {
|
.meal-card img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 180px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-card-content {
|
.meal-card-content {
|
||||||
padding: 16px;
|
padding: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-card-content h3 {
|
.meal-card-content h3 {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 6px;
|
||||||
font-size: 1.2rem;
|
font-size: 1.15rem;
|
||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-card-content p {
|
.meal-card-content p {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-readmore {
|
/* === Läs mer-knapp === */
|
||||||
|
.btn-readmore {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
background: #007d36;
|
background: #007d36;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 8px 12px;
|
padding: 7px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-top: auto;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-readmore:hover {
|
||||||
|
background: #005c27;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Mobilanpassning === */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.meal-gallery-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-gallery-header h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container input[type="text"] {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-published {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-card img {
|
||||||
|
height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-card-content {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-card-content h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-card-content p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-readmore {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-create-meal {
|
||||||
|
background-color: #3399ff;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
margin-top: 12px;
|
font-weight: 500;
|
||||||
}
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-readmore:hover {
|
.btn-create-meal:hover {
|
||||||
background: #005c27;
|
background-color: #2389e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.btn-create-meal {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
204
Aberwyn/wwwroot/css/meal.css
Normal file
204
Aberwyn/wwwroot/css/meal.css
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
.admin-actions a.btn, .admin-actions a.btn-outline {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging {
|
||||||
|
outline: 2px dashed #6a0dad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-image-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-meta {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-image {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-details p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #333;
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #6a0dad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn,
|
||||||
|
.btn-outline {
|
||||||
|
background-color: #6a0dad;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.6rem 1.4rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 0.4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 2px solid #6a0dad;
|
||||||
|
color: #6a0dad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-link {
|
||||||
|
color: #6a0dad;
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-qty {
|
||||||
|
flex: 1 0 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-item {
|
||||||
|
flex: 2 0 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.meal-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-meta {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meal-image {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-qty,
|
||||||
|
.ingredient-item {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn,
|
||||||
|
.btn-outline {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.star-container {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-star {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #ccc;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-star.rated {
|
||||||
|
color: #ffcc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slim {
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #F9FAFB;
|
|
||||||
--bg-mid: #EDF2F7;
|
--bg-mid: #EDF2F7;
|
||||||
--text: #2D3748;
|
--text: #2D3748;
|
||||||
--accent: #3182CE;
|
--accent: #3182CE;
|
||||||
@@ -34,6 +33,10 @@ body {
|
|||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
.meal-menu-page {
|
.meal-menu-page {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
HEADER <20> LEWEL DESIGN (utan meny)
|
HEADER LEWEL DESIGN (utan meny)
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
.top-bar {
|
.top-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -147,25 +147,22 @@ body {
|
|||||||
|
|
||||||
.page-content {
|
.page-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 16px;
|
|
||||||
gap: 16px;
|
|
||||||
background-color: #223344;
|
background-color: #223344;
|
||||||
}
|
width: 100%; /* force full width */
|
||||||
|
max-width: unset;
|
||||||
.sidebar {
|
position: relative;
|
||||||
width: 155px;
|
z-index: 0;
|
||||||
background-color: #f9f9f9;
|
overflow: visible !important;
|
||||||
border-radius: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-panel {
|
.main-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: #ffffff;
|
background: linear-gradient(to bottom right, #f9fafb, #e2e8f0);
|
||||||
border-radius: 10px;
|
border-radius: 0 0 10px 10px;
|
||||||
padding: 20px;
|
overflow: visible;
|
||||||
|
padding: 0px;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -173,11 +170,22 @@ body {
|
|||||||
color: #2C3E50;
|
color: #2C3E50;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
margin-top: 0;
|
||||||
|
position: static !important;
|
||||||
|
z-index: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
NAVIGATIONSLISTA <20> KOMPAKT STIL
|
NAVIGATIONSLISTA <20> KOMPAKT STIL
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
|
/*.sidebar {
|
||||||
|
width: 155px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
.nav-list {
|
.nav-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -210,16 +218,250 @@ body {
|
|||||||
width: 14px;
|
width: 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 54px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||||
|
padding: 5px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
overflow: visible !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.nav-list-horizontal {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.nav-list-horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 5px 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
overflow-y: visible;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
.nav-list-horizontal > li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.nav-list-horizontal li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1F2C3C;
|
||||||
|
padding: 0px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list-horizontal li a:hover {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
RESPONSIVT
|
RESPONSIVT
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
@media (max-width: 768px) {
|
|
||||||
.page-content {
|
|
||||||
flex-direction: column;
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Dropdown
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1F2C3C;
|
||||||
|
padding: 0px 5px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
height: 100%; /* viktigt för vertikal alignment */
|
||||||
|
user-select: none;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle:hover {
|
||||||
|
background-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.dropdown-menu {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
}
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 180px;
|
||||||
|
z-index: 1001;
|
||||||
|
padding: 4px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 90vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown.open .dropdown-menu {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.dropdown-menu li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu li a {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #1F2C3C;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu li a:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
html, body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel {
|
||||||
|
position: relative !important;
|
||||||
|
z-index: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
padding: 5px 8px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list-horizontal {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
gap: 5px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
position: fixed !important;
|
||||||
|
right: 12px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 99999;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 90vw;
|
||||||
|
min-width: 160px; /* valfritt, för att undvika att det blir för smalt */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown.open .dropdown-menu {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown,
|
||||||
|
.auth-menu,
|
||||||
|
.auth-links {
|
||||||
|
position: relative;
|
||||||
|
z-index: 99998;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu li a {
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: #1F2C3C;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu li a:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-lewel {
|
||||||
|
background-color: #3399ff; /* blå accent */
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lewel:hover {
|
||||||
|
background-color: #2389e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lewel-outline {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #3399ff;
|
||||||
|
border: 2px solid #3399ff;
|
||||||
|
padding: 0.5rem 1.2rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lewel-outline:hover {
|
||||||
|
background-color: #e0f0ff;
|
||||||
|
color: #1F2C3C;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 13 KiB |
@@ -2,18 +2,24 @@
|
|||||||
console.log("budget.js loaded");
|
console.log("budget.js loaded");
|
||||||
app.controller('BudgetController', function ($scope, $http) {
|
app.controller('BudgetController', function ($scope, $http) {
|
||||||
$scope.budget = null;
|
$scope.budget = null;
|
||||||
$scope.loading = false;
|
$scope.loading = true;
|
||||||
$scope.error = null;
|
$scope.error = null;
|
||||||
$scope.menuOpen = false;
|
$scope.menuOpen = false;
|
||||||
$scope.chartMode = "pie";
|
$scope.chartMode = "pie";
|
||||||
|
const initialName = window.initialName;
|
||||||
const today = new Date();
|
|
||||||
$scope.selectedYear = today.getFullYear();
|
|
||||||
$scope.selectedMonth = today.getMonth() + 1;
|
|
||||||
$scope.tempMonth = $scope.monthNames?.[today.getMonth()] || "";
|
|
||||||
$scope.tempYear = $scope.selectedYear;
|
|
||||||
|
|
||||||
$scope.monthNames = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
$scope.monthNames = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
||||||
|
$scope.getMonthName = function (month) {
|
||||||
|
return $scope.monthNames[month - 1] || "";
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.selectedYear = window.initialYear || new Date().getFullYear();
|
||||||
|
$scope.selectedMonth = window.initialMonth || new Date().getMonth() + 1;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.tempMonth = $scope.monthNames[$scope.selectedMonth - 1];
|
||||||
|
$scope.tempYear = $scope.selectedYear;
|
||||||
|
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||||
|
|
||||||
$scope.getMonthName = function (month) {
|
$scope.getMonthName = function (month) {
|
||||||
return $scope.monthNames[month - 1] || "";
|
return $scope.monthNames[month - 1] || "";
|
||||||
@@ -42,6 +48,17 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
};
|
};
|
||||||
$scope.menuVisible = true;
|
$scope.menuVisible = true;
|
||||||
};
|
};
|
||||||
|
$scope.updateMonthAndUrl = function () {
|
||||||
|
const year = $scope.selectedYear;
|
||||||
|
const month = $scope.selectedMonth.toString();
|
||||||
|
const newUrl = `/budget/${year}/${month}`;
|
||||||
|
window.history.replaceState(null, '', newUrl); // Uppdaterar URL utan reload
|
||||||
|
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||||
|
$scope.tempMonth = $scope.selectedMonthName;
|
||||||
|
$scope.tempYear = $scope.selectedYear;
|
||||||
|
$scope.loadBudget();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.setItemType = function (item, type) {
|
$scope.setItemType = function (item, type) {
|
||||||
if (type === 'expense') {
|
if (type === 'expense') {
|
||||||
@@ -75,6 +92,39 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
$scope.menuOpen = false;
|
$scope.menuOpen = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
let lastTapTime = 0;
|
||||||
|
|
||||||
|
$scope.handleItemInteraction = function (event, item) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
|
||||||
|
// Ctrl-klick på desktop
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
togglePaymentStatus(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dubbeltap på mobil (inom 400ms)
|
||||||
|
if (now - lastTapTime < 400) {
|
||||||
|
togglePaymentStatus(item);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTapTime = now;
|
||||||
|
};
|
||||||
|
|
||||||
|
function togglePaymentStatus(item) {
|
||||||
|
item.paymentStatus = (item.paymentStatus + 1) % 3;
|
||||||
|
|
||||||
|
$http.put("/api/budget/updatePaymentStatus", {
|
||||||
|
itemId: item.id,
|
||||||
|
status: item.paymentStatus
|
||||||
|
}).then(() => {
|
||||||
|
$scope.showToast("Betalstatus uppdaterad");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$scope.showToast = function (message, isError = false) {
|
$scope.showToast = function (message, isError = false) {
|
||||||
const toast = document.createElement("div");
|
const toast = document.createElement("div");
|
||||||
@@ -92,10 +142,21 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
$scope.error = null;
|
$scope.error = null;
|
||||||
$scope.budget = null;
|
$scope.budget = null;
|
||||||
|
$scope.budgetNotFound = false;
|
||||||
|
|
||||||
$http.get(`/api/budget/${$scope.selectedYear}/${$scope.selectedMonth}`)
|
const useName = typeof initialName === 'string' && initialName !== "null" && initialName !== "";
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (useName) {
|
||||||
|
url = `/api/budget/byname/${initialName}`;
|
||||||
|
} else {
|
||||||
|
url = `/api/budget/${$scope.selectedYear}/${$scope.selectedMonth}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.get(url)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
const raw = response.data;
|
const raw = response.data;
|
||||||
|
|
||||||
if (raw && raw.Categories) {
|
if (raw && raw.Categories) {
|
||||||
const categories = raw.Categories.map(cat => ({
|
const categories = raw.Categories.map(cat => ({
|
||||||
id: cat.Id,
|
id: cat.Id,
|
||||||
@@ -103,6 +164,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
color: cat.Color,
|
color: cat.Color,
|
||||||
editing: false,
|
editing: false,
|
||||||
allowDrag: false,
|
allowDrag: false,
|
||||||
|
order: cat.Order ?? 0,
|
||||||
items: (cat.Items || []).map((item, index) => {
|
items: (cat.Items || []).map((item, index) => {
|
||||||
const definition = $scope.itemDefinitions.find(d => d.id === item.BudgetItemDefinitionId || d.Id === item.BudgetItemDefinitionId);
|
const definition = $scope.itemDefinitions.find(d => d.id === item.BudgetItemDefinitionId || d.Id === item.BudgetItemDefinitionId);
|
||||||
return {
|
return {
|
||||||
@@ -113,26 +175,39 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
includeInSummary: item.IncludeInSummary === true,
|
includeInSummary: item.IncludeInSummary === true,
|
||||||
order: item.Order ?? index,
|
order: item.Order ?? index,
|
||||||
budgetItemDefinitionId: item.BudgetItemDefinitionId,
|
budgetItemDefinitionId: item.BudgetItemDefinitionId,
|
||||||
definitionName: definition?.Name || null
|
definitionName: definition?.Name || null,
|
||||||
|
paymentStatus: item.PaymentStatus
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.order - b.order)
|
}).sort((a, b) => a.order - b.order)
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$scope.budget = {
|
$scope.budget = {
|
||||||
id: raw.Id,
|
id: raw.Id,
|
||||||
|
name: raw.Name || null,
|
||||||
year: raw.Year,
|
year: raw.Year,
|
||||||
month: raw.Month,
|
month: raw.Month,
|
||||||
categories: categories.sort((a, b) => a.order - b.order)
|
categories: categories.sort((a, b) => a.order - b.order)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.budgetNotFound = false;
|
||||||
|
|
||||||
|
if (!useName) {
|
||||||
|
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$scope.budget = { categories: [] };
|
$scope.budget = { categories: [] };
|
||||||
|
$scope.budgetNotFound = true;
|
||||||
}
|
}
|
||||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
$scope.budget = { categories: [] };
|
$scope.budget = {
|
||||||
|
name: useName ? initialName : null,
|
||||||
|
year: useName ? null : $scope.selectedYear,
|
||||||
|
month: useName ? null : $scope.selectedMonth,
|
||||||
|
categories: []
|
||||||
|
};
|
||||||
|
$scope.budgetNotFound = true;
|
||||||
} else {
|
} else {
|
||||||
$scope.error = "Kunde inte ladda budgetdata.";
|
$scope.error = "Kunde inte ladda budgetdata.";
|
||||||
$scope.showToast("Fel vid laddning av budgetdata", true);
|
$scope.showToast("Fel vid laddning av budgetdata", true);
|
||||||
@@ -145,6 +220,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.saveCategory = function (category) {
|
$scope.saveCategory = function (category) {
|
||||||
if (category.newItemName && category.newItemAmount) {
|
if (category.newItemName && category.newItemAmount) {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
@@ -286,7 +362,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.getLeftover = function () {
|
$scope.getLeftover = function () {
|
||||||
return $scope.getTotalIncome() - $scope.getTotalExpense();
|
return $scope.getTotalIncome() - $scope.getTotalExpense() - $scope.getTotalSaving();
|
||||||
};
|
};
|
||||||
|
|
||||||
function positionAddItemPopup(popup, triggerButton) {
|
function positionAddItemPopup(popup, triggerButton) {
|
||||||
@@ -307,12 +383,12 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
if (monthIndex >= 0 && $scope.tempYear) {
|
if (monthIndex >= 0 && $scope.tempYear) {
|
||||||
$scope.selectedMonth = monthIndex + 1;
|
$scope.selectedMonth = monthIndex + 1;
|
||||||
$scope.selectedYear = parseInt($scope.tempYear);
|
$scope.selectedYear = parseInt($scope.tempYear);
|
||||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
$scope.updateMonthAndUrl();
|
||||||
$scope.showMonthPicker = false;
|
$scope.showMonthPicker = false;
|
||||||
$scope.loadBudget();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.previousMonth = function () {
|
$scope.previousMonth = function () {
|
||||||
if ($scope.selectedMonth === 1) {
|
if ($scope.selectedMonth === 1) {
|
||||||
$scope.selectedMonth = 12;
|
$scope.selectedMonth = 12;
|
||||||
@@ -320,12 +396,10 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
} else {
|
} else {
|
||||||
$scope.selectedMonth--;
|
$scope.selectedMonth--;
|
||||||
}
|
}
|
||||||
$scope.tempMonth = $scope.getMonthName($scope.selectedMonth);
|
$scope.updateMonthAndUrl();
|
||||||
$scope.tempYear = $scope.selectedYear;
|
|
||||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
|
||||||
$scope.loadBudget();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.nextMonth = function () {
|
$scope.nextMonth = function () {
|
||||||
if ($scope.selectedMonth === 12) {
|
if ($scope.selectedMonth === 12) {
|
||||||
$scope.selectedMonth = 1;
|
$scope.selectedMonth = 1;
|
||||||
@@ -333,12 +407,10 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
} else {
|
} else {
|
||||||
$scope.selectedMonth++;
|
$scope.selectedMonth++;
|
||||||
}
|
}
|
||||||
$scope.tempMonth = $scope.getMonthName($scope.selectedMonth);
|
$scope.updateMonthAndUrl();
|
||||||
$scope.tempYear = $scope.selectedYear;
|
|
||||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
|
||||||
$scope.loadBudget();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.handleCategoryDrop = function (data, targetCategory) {
|
$scope.handleCategoryDrop = function (data, targetCategory) {
|
||||||
if (data.type !== 'category') return; // ⛔ stoppa om det är ett item-drag
|
if (data.type !== 'category') return; // ⛔ stoppa om det är ett item-drag
|
||||||
|
|
||||||
@@ -485,14 +557,13 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
};
|
};
|
||||||
$scope.createNewCategory = function () {
|
$scope.createNewCategory = function () {
|
||||||
const defaultName = "Ny kategori";
|
const defaultName = "Ny kategori";
|
||||||
const newOrder = $scope.budget.categories.length; // sist i listan
|
const newOrder = $scope.budget.categories.length;
|
||||||
|
|
||||||
const newCategory = {
|
const newCategory = {
|
||||||
name: defaultName,
|
name: defaultName,
|
||||||
color: "#666666",
|
color: "#666666",
|
||||||
year: $scope.selectedYear,
|
order: newOrder,
|
||||||
month: $scope.selectedMonth,
|
budgetPeriodId: $scope.budget.id // <- 💡 den viktiga raden
|
||||||
order: newOrder
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$http.post("/api/budget/category", newCategory)
|
$http.post("/api/budget/category", newCategory)
|
||||||
@@ -517,6 +588,8 @@ app.controller('BudgetController', function ($scope, $http) {
|
|||||||
$scope.showToast("Fel vid skapande av kategori", true);
|
$scope.showToast("Fel vid skapande av kategori", true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.addItem = function (category) {
|
$scope.addItem = function (category) {
|
||||||
if (!category.newItemName || !category.newItemAmount) return;
|
if (!category.newItemName || !category.newItemAmount) return;
|
||||||
|
|
||||||
@@ -766,6 +839,28 @@ $scope.addItemFromDefinition = function (cat) {
|
|||||||
return cat.items.some(i => i.isExpense);
|
return cat.items.some(i => i.isExpense);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.createEmptyBudget = function () {
|
||||||
|
if (!$scope.budget || !$scope.budget.name) {
|
||||||
|
$scope.showToast("Ogiltigt budgetnamn.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dto = {
|
||||||
|
name: $scope.budget.name
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post('/api/budget', dto)
|
||||||
|
.then(() => {
|
||||||
|
$scope.showToast("Ny budget skapad.");
|
||||||
|
$scope.loadBudget(); // ladda om efter skapandet
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Fel vid skapande:", error);
|
||||||
|
$scope.showToast("Kunde inte skapa budget.");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
$scope.drawCategoryChart = function () {
|
$scope.drawCategoryChart = function () {
|
||||||
const ctx = document.getElementById("expenseChart");
|
const ctx = document.getElementById("expenseChart");
|
||||||
if (!ctx || !$scope.budget?.categories) return;
|
if (!ctx || !$scope.budget?.categories) return;
|
||||||
@@ -824,6 +919,63 @@ $scope.addItemFromDefinition = function (cat) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Import
|
||||||
|
$scope.importing = false;
|
||||||
|
$scope.importText = '';
|
||||||
|
$scope.importPreview = [];
|
||||||
|
$scope.importTargetCategory = null;
|
||||||
|
|
||||||
|
$scope.openImportModule = function () {
|
||||||
|
// Välj första redigerbara kategori som standard (eller be om val senare)
|
||||||
|
const editable = $scope.budget.categories.find(c => c.editing);
|
||||||
|
if (!editable) {
|
||||||
|
alert("Redigera en kategori först!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$scope.importTargetCategory = editable;
|
||||||
|
$scope.importText = '';
|
||||||
|
$scope.importPreview = [];
|
||||||
|
$scope.importing = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancelImport = function () {
|
||||||
|
$scope.importing = false;
|
||||||
|
$scope.importText = '';
|
||||||
|
$scope.importPreview = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.parseImportText = function () {
|
||||||
|
const lines = $scope.importText.trim().split('\n');
|
||||||
|
$scope.importPreview = lines.map(l => {
|
||||||
|
const parts = l.trim().match(/^(.+?)\s+(-?\d+(?:[,.]\d+)?)/);
|
||||||
|
return {
|
||||||
|
name: parts?.[1] || '',
|
||||||
|
amount: parseFloat((parts?.[2] || '0').replace(',', '.'))
|
||||||
|
};
|
||||||
|
}).filter(item => item.name && !isNaN(item.amount));
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.applyImport = function () {
|
||||||
|
const cat = $scope.importTargetCategory;
|
||||||
|
$scope.importPreview.forEach(p => {
|
||||||
|
cat.items.push({
|
||||||
|
id: -1 * Math.floor(Math.random() * 1000000), // temporärt ID
|
||||||
|
name: p.name,
|
||||||
|
amount: p.amount,
|
||||||
|
isExpense: true,
|
||||||
|
includeInSummary: true,
|
||||||
|
paymentStatus: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.importing = false;
|
||||||
|
$scope.importText = '';
|
||||||
|
$scope.importPreview = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$scope.loading = true;
|
||||||
$scope.loadItemDefinitions().then(() => {
|
$scope.loadItemDefinitions().then(() => {
|
||||||
$scope.loadBudget();
|
$scope.loadBudget();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ angular.module('mealMenuApp', ['ngSanitize'])
|
|||||||
});
|
});
|
||||||
|
|
||||||
}).catch(err => console.error("Fel vid hämtning av meny:", err));
|
}).catch(err => console.error("Fel vid hämtning av meny:", err));
|
||||||
$scope.loadSchoolMeals(); // Lägg till här
|
//$scope.loadSchoolMeals(); // Lägg till här
|
||||||
|
|
||||||
};
|
};
|
||||||
$scope.schoolMeals = [];
|
$scope.schoolMeals = [];
|
||||||
@@ -94,8 +94,13 @@ angular.module('mealMenuApp', ['ngSanitize'])
|
|||||||
|
|
||||||
|
|
||||||
$scope.openMeal = function (mealId) {
|
$scope.openMeal = function (mealId) {
|
||||||
if (!mealId) return;
|
if (mealId) {
|
||||||
window.open('/Meal/View/' + mealId, '_blank');
|
const page = document.querySelector('.meal-menu-page');
|
||||||
|
page.classList.add('fade-out');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/Meal/View/" + mealId;
|
||||||
|
}, 400); // matchar CSS-transition
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getDayImage = function (day) {
|
$scope.getDayImage = function (day) {
|
||||||
|
|||||||
@@ -6,13 +6,102 @@ if ('serviceWorker' in navigator) {
|
|||||||
navigator.serviceWorker.register('/service-worker.js')
|
navigator.serviceWorker.register('/service-worker.js')
|
||||||
.then(function (registration) {
|
.then(function (registration) {
|
||||||
//console.log('✅ Service Worker registrerad med scope:', registration.scope);
|
//console.log('✅ Service Worker registrerad med scope:', registration.scope);
|
||||||
subscribeToPush().catch(console.error);
|
//subscribeToPush().catch(console.error);
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
console.log('❌ Service Worker-registrering misslyckades:', error);
|
console.log('❌ Service Worker-registrering misslyckades:', error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const dropdowns = document.querySelectorAll(".dropdown");
|
||||||
|
const globalContainer = document.getElementById("global-dropdown-container");
|
||||||
|
|
||||||
|
dropdowns.forEach((dropdown, index) => {
|
||||||
|
const toggle = dropdown.querySelector(".dropdown-toggle");
|
||||||
|
const menu = dropdown.querySelector(".dropdown-menu");
|
||||||
|
|
||||||
|
if (!toggle || !menu) return;
|
||||||
|
|
||||||
|
let originalParent = dropdown;
|
||||||
|
|
||||||
|
toggle.addEventListener("click", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const isOpen = dropdown.classList.contains("open");
|
||||||
|
|
||||||
|
// Stäng andra
|
||||||
|
document.querySelectorAll(".dropdown.open").forEach(d => {
|
||||||
|
d.classList.remove("open");
|
||||||
|
const m = d.querySelector(".dropdown-menu");
|
||||||
|
if (m && d !== dropdown) {
|
||||||
|
d.appendChild(m);
|
||||||
|
resetStyles(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdown.classList.toggle("open", !isOpen);
|
||||||
|
|
||||||
|
if (!isOpen && window.innerWidth <= 768) {
|
||||||
|
const rect = toggle.getBoundingClientRect();
|
||||||
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||||
|
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
||||||
|
|
||||||
|
let top = rect.bottom + scrollTop - 20;
|
||||||
|
let left = rect.left + scrollLeft;
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
const mw = menu.offsetWidth;
|
||||||
|
|
||||||
|
if (left + mw > vw) {
|
||||||
|
left = vw - mw - 10;
|
||||||
|
}
|
||||||
|
if (left < 10) left = 10;
|
||||||
|
|
||||||
|
// Flytta till global container
|
||||||
|
globalContainer.appendChild(menu);
|
||||||
|
|
||||||
|
Object.assign(menu.style, {
|
||||||
|
position: "fixed",
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
zIndex: "99999",
|
||||||
|
display: "flex"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Återställ till original
|
||||||
|
originalParent.appendChild(menu);
|
||||||
|
resetStyles(menu);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", () => {
|
||||||
|
document.querySelectorAll(".dropdown.open").forEach(d => {
|
||||||
|
d.classList.remove("open");
|
||||||
|
const m = d.querySelector(".dropdown-menu");
|
||||||
|
if (m) {
|
||||||
|
d.appendChild(m);
|
||||||
|
resetStyles(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Töm extra container
|
||||||
|
if (globalContainer) globalContainer.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetStyles(menu) {
|
||||||
|
Object.assign(menu.style, {
|
||||||
|
position: "",
|
||||||
|
top: "",
|
||||||
|
left: "",
|
||||||
|
zIndex: "",
|
||||||
|
display: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function subscribeToPush() {
|
async function subscribeToPush() {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker.ready;
|
||||||
@@ -38,6 +127,11 @@ async function subscribeToPush() {
|
|||||||
|
|
||||||
async function enablePush() {
|
async function enablePush() {
|
||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
|
const existingSub = await registration.pushManager.getSubscription();
|
||||||
|
if (existingSub) {
|
||||||
|
alert("🔔 Du är redan prenumererad på notiser.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (permission !== "granted") {
|
if (permission !== "granted") {
|
||||||
alert("Du måste tillåta notiser för att få push.");
|
alert("Du måste tillåta notiser för att få push.");
|
||||||
|
|||||||
Reference in New Issue
Block a user