Mostly css changes and welcome page thumbnails
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -3,13 +3,23 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
[Authorize(Roles = "Budget")]
|
||||
public class BudgetController : Controller
|
||||
{
|
||||
[Authorize(Roles = "Budget")]
|
||||
public IActionResult Index()
|
||||
[Route("budget/{year:int}/{month:int}")]
|
||||
public IActionResult Index(int year, int month)
|
||||
{
|
||||
ViewData["HideSidebar"] = true;
|
||||
ViewBag.Year = year;
|
||||
ViewBag.Month = month;
|
||||
return View();
|
||||
}
|
||||
|
||||
// För fallback när ingen månad/år anges
|
||||
[Route("budget")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return RedirectToAction("Index", new { year = now.Year, month = now.Month });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,6 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
||||
int week = ISOWeek.GetWeekOfYear(date);
|
||||
int year = date.Year;
|
||||
|
||||
// Gör om till ISO 8601: Måndag = 1, Söndag = 7
|
||||
int dayOfWeek = (int)date.DayOfWeek;
|
||||
if (dayOfWeek == 0) dayOfWeek = 7;
|
||||
|
||||
@@ -361,20 +360,30 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
||||
|
||||
var allMeals = _context.Meals
|
||||
.Where(m => mealIds.Contains(m.Id))
|
||||
.ToDictionary(m => m.Id, m => m.Name);
|
||||
.ToDictionary(m => m.Id);
|
||||
|
||||
if (menu.BreakfastMealId.HasValue && allMeals.TryGetValue(menu.BreakfastMealId.Value, out var breakfast))
|
||||
menu.BreakfastMealName = breakfast;
|
||||
if (menu.LunchMealId.HasValue && allMeals.TryGetValue(menu.LunchMealId.Value, out var lunch))
|
||||
menu.LunchMealName = lunch;
|
||||
if (menu.DinnerMealId.HasValue && allMeals.TryGetValue(menu.DinnerMealId.Value, out var dinner))
|
||||
menu.DinnerMealName = dinner;
|
||||
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<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var results = new List<WeeklyMenu>();
|
||||
|
||||
@@ -43,6 +43,9 @@ public class WeeklyMenu
|
||||
public int WeekNumber { get; set; }
|
||||
public int Year { 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? LunchMealName { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@{
|
||||
ViewData["Title"] = "Budget";
|
||||
}
|
||||
|
||||
<div ng-app="budgetApp" ng-controller="BudgetController" class="budget-page" ng-if="!loading">
|
||||
<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;">
|
||||
@@ -93,7 +94,7 @@
|
||||
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>
|
||||
<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">
|
||||
@@ -129,11 +130,10 @@
|
||||
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" />
|
||||
<span ng-if="!cat.editing" title="{{ item.definitionName }}">{{ item.name }}</span>
|
||||
<!-- <span ng-if="!cat.editing">#{{ item.definitionName }}</span>-->
|
||||
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
|
||||
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
|
||||
<input type="text" class="item-label" ng-model="item.name" ng-if="cat.editing" />
|
||||
<span ng-if="!cat.editing" class="item-label" title="{{ item.name }}">{{ item.name }}</span>
|
||||
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
|
||||
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
|
||||
|
||||
<!-- 3-pricksmeny -->
|
||||
<div class="item-menu-container" ng-if="cat.editing" style="position: relative;">
|
||||
@@ -231,5 +231,9 @@
|
||||
<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>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||
<script>
|
||||
window.initialYear = @ViewBag.Year;
|
||||
window.initialMonth = @ViewBag.Month;
|
||||
</script>
|
||||
<script src="~/js/budget.js"></script>
|
||||
<script src="~/js/budget-dragdrop.js"></script>
|
||||
@@ -18,27 +18,87 @@
|
||||
@if (Model != null)
|
||||
{
|
||||
<div class="meal-lines">
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) {
|
||||
<p><strong>Frukost:</strong> @Model.BreakfastMealName</p>
|
||||
<div class="meal-line">
|
||||
@if (Model.BreakfastThumbnail != null)
|
||||
{
|
||||
var b64 = Convert.ToBase64String(Model.BreakfastThumbnail);
|
||||
<button type="button" class="thumb-button" onclick="showLargeImage('@b64', '@Model.BreakfastMealName')">
|
||||
<img class="meal-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.BreakfastMealName" />
|
||||
</button>
|
||||
}
|
||||
<p><strong>Frukost:</strong> @Model.BreakfastMealName</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) {
|
||||
<p><strong>Lunch:</strong> @Model.LunchMealName</p>
|
||||
<div class="meal-line">
|
||||
@if (Model.LunchThumbnail != null)
|
||||
{
|
||||
var b64 = Convert.ToBase64String(Model.LunchThumbnail);
|
||||
<button type="button" class="thumb-button" onclick="showLargeImage('@b64', '@Model.LunchMealName')">
|
||||
<img class="meal-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.LunchMealName" />
|
||||
</button>
|
||||
}
|
||||
<p><strong>Lunch:</strong> @Model.LunchMealName</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.DinnerMealName)) {
|
||||
<p><strong>Middag:</strong> @Model.DinnerMealName</p>
|
||||
<div class="meal-line">
|
||||
@if (Model.DinnerThumbnail != null)
|
||||
{
|
||||
var b64 = Convert.ToBase64String(Model.DinnerThumbnail);
|
||||
<button type="button" class="thumb-button" onclick="showLargeImage('@b64', '@Model.DinnerMealName')">
|
||||
<img class="meal-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.DinnerMealName" />
|
||||
</button>
|
||||
}
|
||||
<p><strong>Middag:</strong> @Model.DinnerMealName</p>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
@@ -80,3 +80,80 @@
|
||||
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 {
|
||||
--text-main: #1E293B;
|
||||
--text-sub: #64748B;
|
||||
--bg-main: #f9fafb;
|
||||
--bg-card: #f1f5f9;
|
||||
--border-color: #e5e7eb;
|
||||
--card-income: #f97316;
|
||||
--text-main: #1F2937; /* Mörkblågrå – tydlig men mjuk */
|
||||
--text-sub: #64748B; /* Sekundär gråblå */
|
||||
--bg-main: #1F2C3C; /* SID-bakgrund */
|
||||
--bg-card: #dbe3ec;
|
||||
--bg-card-summary: #d6dde6;
|
||||
--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-savings: #facc15;
|
||||
--card-leftover: #86efac;
|
||||
--card-leftover: #4ade80;
|
||||
--btn-edit: #3b82f6;
|
||||
--btn-check: #10b981;
|
||||
--btn-delete: #ef4444;
|
||||
}
|
||||
|
||||
|
||||
|
||||
body {
|
||||
background-color: var(--bg-main);
|
||||
color: var(--text-main);
|
||||
@@ -105,6 +110,7 @@ body {
|
||||
height: 350px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
color: var(--text-main);
|
||||
background-color: var(--bg-card);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
@@ -134,6 +140,7 @@ body {
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-radius: 0 0 12px 12px;
|
||||
flex-shrink: 0;
|
||||
background: var(--bg-card-summary);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -521,7 +528,10 @@ color: var(--btn-check);
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
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;
|
||||
background-color: var(--bg-card);
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.budget-summary-box h3,
|
||||
@@ -601,7 +611,18 @@ canvas {
|
||||
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) {
|
||||
|
||||
.budget-overview-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -634,10 +655,51 @@ canvas {
|
||||
max-width: 100%;
|
||||
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) {
|
||||
.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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -662,6 +724,8 @@ canvas {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
color: #1E293B; /* djupare textfärg */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chart-area canvas {
|
||||
|
||||
@@ -7,13 +7,17 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.menuOpen = false;
|
||||
$scope.chartMode = "pie";
|
||||
|
||||
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.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) {
|
||||
return $scope.monthNames[month - 1] || "";
|
||||
@@ -42,6 +46,17 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
};
|
||||
$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) {
|
||||
if (type === 'expense') {
|
||||
@@ -307,12 +322,12 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
if (monthIndex >= 0 && $scope.tempYear) {
|
||||
$scope.selectedMonth = monthIndex + 1;
|
||||
$scope.selectedYear = parseInt($scope.tempYear);
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
$scope.updateMonthAndUrl();
|
||||
$scope.showMonthPicker = false;
|
||||
$scope.loadBudget();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.previousMonth = function () {
|
||||
if ($scope.selectedMonth === 1) {
|
||||
$scope.selectedMonth = 12;
|
||||
@@ -320,12 +335,10 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
} else {
|
||||
$scope.selectedMonth--;
|
||||
}
|
||||
$scope.tempMonth = $scope.getMonthName($scope.selectedMonth);
|
||||
$scope.tempYear = $scope.selectedYear;
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
$scope.loadBudget();
|
||||
$scope.updateMonthAndUrl();
|
||||
};
|
||||
|
||||
|
||||
$scope.nextMonth = function () {
|
||||
if ($scope.selectedMonth === 12) {
|
||||
$scope.selectedMonth = 1;
|
||||
@@ -333,12 +346,10 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
} else {
|
||||
$scope.selectedMonth++;
|
||||
}
|
||||
$scope.tempMonth = $scope.getMonthName($scope.selectedMonth);
|
||||
$scope.tempYear = $scope.selectedYear;
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
$scope.loadBudget();
|
||||
$scope.updateMonthAndUrl();
|
||||
};
|
||||
|
||||
|
||||
$scope.handleCategoryDrop = function (data, targetCategory) {
|
||||
if (data.type !== 'category') return; // ⛔ stoppa om det är ett item-drag
|
||||
|
||||
|
||||
Reference in New Issue
Block a user