New themese and night/day mode
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-05-12 15:39:12 +02:00
parent 3ff2833101
commit cc213dce1b
7 changed files with 297 additions and 268 deletions

View File

@@ -141,7 +141,7 @@ namespace Aberwyn.Data
using (var connection = GetConnection())
{
connection.Open();
string query = "SELECT Id, Name FROM Meals";
string query = "SELECT Id, Name, ImageUrl FROM Meals";
using (var cmd = new MySqlCommand(query, connection))
{
using (var reader = cmd.ExecuteReader())
@@ -151,7 +151,8 @@ namespace Aberwyn.Data
meals.Add(new Meal
{
Id = reader.GetInt32("Id"),
Name = reader.GetString("Name")
Name = reader.GetString("Name"),
ImageUrl = reader.IsDBNull(reader.GetOrdinal("ImageUrl")) ? null : reader.GetString(reader.GetOrdinal("ImageUrl"))
});
}
}
@@ -167,7 +168,7 @@ namespace Aberwyn.Data
{
connection.Open();
string query = @"
SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt
SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl
FROM Meals
ORDER BY CreatedAt DESC";
@@ -184,6 +185,7 @@ namespace Aberwyn.Data
ProteinType = reader.IsDBNull(reader.GetOrdinal("ProteinType")) ? null : reader.GetString(reader.GetOrdinal("ProteinType")),
CarbType = reader.IsDBNull(reader.GetOrdinal("CarbType")) ? null : reader.GetString(reader.GetOrdinal("CarbType")),
RecipeUrl = reader.IsDBNull(reader.GetOrdinal("RecipeUrl")) ? null : reader.GetString(reader.GetOrdinal("RecipeUrl")),
ImageUrl = reader.IsDBNull(reader.GetOrdinal("ImageUrl")) ? null : reader.GetString(reader.GetOrdinal("ImageUrl")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
});
}

View File

@@ -1,61 +1,33 @@
@model Aberwyn.Models.MenuViewModel
@{
var hideSidebar = Context.Request.Path.Value.EndsWith("/nosidebar", StringComparison.OrdinalIgnoreCase);
Layout = hideSidebar ? null : "_Layout";
}
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="sv" ng-app="mealMenuApp">
<head>
<meta charset="utf-8" />
<title>Veckomeny</title>
<meta charset="utf-8" />
<title>Veckomeny</title>
<!-- AngularJS -->
<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-sanitize.js"></script>
<script src="~/js/menu.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-sanitize.js"></script>
<script src="~/js/menu.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="~/css/meal-menu.css" />
<style>
.view-selector { position: absolute; top: 24px; right: 24px; }
.view-selector select { padding: 6px 10px; border-radius: 4px; border: none; background: #546E7A; color: #ECEFF1; font-size: 0.95rem; cursor: pointer; }
.list-view .day-item { background: #37474F; margin-bottom: 16px; padding: 16px; border-radius: 8px; }
.list-view .day-header { font-size: 1.4rem; color: #FFEB3B; margin-bottom: 8px; position: relative; }
.list-view .day-header::after { content: ''; position: absolute; bottom: -4px; left: 0; width: 40px; height: 3px; background: #FFEB3B; border-radius: 2px; }
.meal-selection { margin: 6px 0; font-size: 1rem; }
.meal-selection a { color: #ECEFF1; text-decoration: none; }
.meal-selection a:hover { text-decoration: underline; }
.not-assigned { color: #B0BEC5; font-style: italic; }
.card-view .card-container { display: grid; grid-template-columns: repeat(auto-fill,minmax(180px,1fr)); gap: 16px; }
.meal-card { position: relative; height: 180px; color: #fff; background-size: cover; background-position: center; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 8px rgba(0,0,0,0.3); display: flex; align-items: flex-end; cursor: pointer; }
.meal-card::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to top,rgba(0,0,0,0.7),transparent); }
.card-content { position: relative; padding: 12px; z-index: 1; width: 100%; }
.card-content .day { font-size: 1.2rem; font-weight: bold; margin-bottom: 6px; }
.card-content .meal { font-size: 0.9rem; line-height: 1.2; }
</style>
<link rel="stylesheet" href="~/css/menu.css" />
</head>
<body ng-controller="MealMenuController" ng-init="viewMode='list'">
<div class="meal-menu-page" style="position:relative">
<div class="meal-menu-page">
<h1>Veckomeny</h1>
<!-- Vecko-navigering -->
<div class="date-picker">
<button ng-click="goToPreviousWeek()">← Föregående vecka</button>
<span>Vecka {{selectedWeek}} {{selectedYear}}</span>
<button ng-click="goToNextWeek()">Nästa vecka →</button>
</div>
<!-- Vy-väljare -->
<div class="view-selector">
<label for="viewModeSelect">Visa:</label>
<select id="viewModeSelect" ng-model="viewMode">
<option value="list">Lista</option>
<option value="card">Kort</option>
</select>
<button id="toggle-view" class="toggle-view-btn" ng-click="toggleView()" title="Byt vy">🗒️</button>
<button id="toggle-theme" class="theme-toggle-btn" title="Byt tema">
🌙
</button>
</div>
<!-- Vy: List eller Kort -->
<div ng-switch="viewMode">
<div ng-switch-when="list" class="list-view">
<div ng-repeat="day in daysOfWeek" class="day-item">
@@ -71,16 +43,15 @@
<a href="/Meal/View/{{menu[day].dinnerMealId}}" target="_blank"><strong>Middag:</strong> {{menu[day].dinnerMealName}}</a>
</div>
</div>
<div ng-if="!menu[day]"><span class="not-assigned">Inte bestämd</span></div>
<div ng-if="!menu[day]">
<span class="not-assigned">Inte bestämd</span>
</div>
</div>
</div>
<div ng-switch-when="card" class="card-view">
<div class="card-container">
<div ng-repeat="day in daysOfWeek"
class="meal-card"
ng-style="{'background-image':'url('+getDayImage(day)+')'}"
ng-click="openMeal(getMealIdByDay(day))">
<div ng-repeat="day in daysOfWeek" class="meal-card" ng-class="ng-attr-data-has-image" ng-style="{'background-image':'url('+getDayImage(day)+')'}" ng-click="openMeal(getMealIdByDay(day))">
<div class="card-content">
<div class="day">{{day}}</div>
<div class="meal" ng-if="menu[day].breakfastMealName">Frukost: {{menu[day].breakfastMealName}}</div>

View File

@@ -127,7 +127,8 @@
/* Dag-cell med ikonknapp */
.day-cell {
max-width: 0px;
min-width: 25px;
max-width: 20px;
padding: 8px 12px;
font-weight: bold;
vertical-align: top;

View File

@@ -1,214 +0,0 @@
/* Meal Menu Page Styles */
.meal-menu-page {
background-color: #2A3A48; /* Dark background to match theme */
padding: 30px 60px; /* Add more padding to the sides */
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
max-width: 900px; /* Adjust width to provide more space */
margin: 0 auto;
color: #B0BEC5; /* Light gray text */
}
.meal-menu-page h1 {
font-size: 2rem;
text-align: center;
color: #FFFFFF;
margin-bottom: 20px;
}
/* Date Picker */
.date-picker {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 30px;
gap: 20px;
}
.date-picker span {
font-size: 1.2rem;
color: #B0BEC5;
}
.date-picker button {
background-color: #4A6572;
color: #ffffff;
padding: 12px 25px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.date-picker button:hover {
background-color: #3A4E62;
}
/* Mode Toggle */
.mode-toggle {
text-align: center;
margin-bottom: 30px;
}
.mode-toggle button {
background-color: #4CAF50;
color: #ffffff;
padding: 12px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
}
.mode-toggle button:hover {
background-color: #45a049;
}
/* Meal Menu Container - Vertical Layout */
.meal-menu-container {
display: flex;
flex-direction: column; /* Align the days vertically */
gap: 20px;
justify-content: flex-start;
}
/* Day Block Styling */
.day-block {
background-color: #1F2C3C; /* Darker background for day blocks */
padding: 20px;
border-radius: 10px;
width: 100%;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Day Header with Line */
.day-header {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 15px;
color: #FFEB3B; /* Brighter yellow for the text */
position: relative; /* Allows absolute positioning for the line */
}
.day-header::after {
content: "";
position: absolute;
bottom: 0; /* Place it at the bottom of the header */
left: 0;
width: 100%; /* Make the line span across the header */
height: 2px; /* Thickness of the line */
background-color: #FFEB3B; /* Yellow line for contrast */
border-radius: 2px; /* Rounded corners for the line */
}
/* Meal Selection */
.meal-selection {
font-size: 1rem;
color: #B0BEC5;
margin-bottom: 10px;
}
.meal-selection strong {
color: #FFFFFF;
}
.meal-selection span {
color: #FFEB3B; /* Highlight the meal names */
}
/* Edit Mode Styling */
.edit-mode select {
background-color: #444;
color: #fff;
border: none;
padding: 10px;
border-radius: 6px;
width: 100%;
font-size: 1rem;
margin-top: 5px;
cursor: pointer;
}
.edit-mode select:focus {
outline: none;
box-shadow: 0 0 8px rgba(66, 133, 244, 0.8);
}
/* Save Button */
.save-menu-button {
display: block;
background-color: #FF9800;
color: #fff;
padding: 12px 25px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
width: 200px;
margin: 30px auto;
text-align: center;
transition: background-color 0.3s;
}
.save-menu-button:hover {
background-color: #F57C00;
}
/* New Meal Form Styling */
.new-meal-form {
display: flex;
flex-direction: column; /* Stack input fields vertically */
gap: 15px; /* Space between fields */
padding: 20px;
background-color: #1F2C3C; /* Dark background to match theme */
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
max-width: 400px; /* Control the width of the form */
margin: 0 auto;
}
/* Input Fields */
.new-meal-form input,
.new-meal-form select,
.new-meal-form textarea {
background-color: #444; /* Dark background for inputs */
color: #fff; /* White text */
border: none;
padding: 12px 15px;
border-radius: 6px;
font-size: 1rem;
width: 100%; /* Ensure inputs take full width */
}
/* Focused Input Fields */
.new-meal-form input:focus,
.new-meal-form select:focus,
.new-meal-form textarea:focus {
outline: none;
box-shadow: 0 0 8px rgba(66, 133, 244, 0.8); /* Blue focus highlight */
}
/* Submit Button */
.new-meal-form button {
background-color: #FF9800; /* Button color */
color: #fff;
padding: 12px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
width: 100%; /* Button takes full width */
}
/* Button Hover */
.new-meal-form button:hover {
background-color: #F57C00;
}
.meal-hover {
cursor: pointer;
}

View File

@@ -0,0 +1,204 @@
:root {
--bg: #F9FAFB;
--bg-mid: #EDF2F7;
--text: #2D3748;
--accent: #3182CE;
--card-bg: #ffffff;
--border-radius: 10px;
--spacing: 20px;
--font-size: 1rem;
--faint-text: #718096;
--subtle-text: #4A5568;
}
[data-theme="dark"] {
--bg: #1F2C3C;
--bg-mid: #2E3C4F;
--text: #E0E6ED;
--accent: #3182CE;
--card-bg: #2A3A48;
--faint-text: #A0AAB5;
--subtle-text: #D0DCE4;
}
body {
margin: 0;
font-family: "Segoe UI", sans-serif;
background-color: var(--bg);
color: var(--text);
}
.meal-menu-page {
position: relative;
padding: var(--spacing);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
background: radial-gradient(circle at center, var(--bg-mid), var(--bg) 80%);
}
.meal-menu-page > * {
position: relative;
z-index: 1;
width: 100%;
max-width: 960px;
}
h1 {
font-size: 2rem;
margin-bottom: var(--spacing);
text-align: center;
color: var(--accent);
}
.date-picker {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin-bottom: var(--spacing);
font-size: var(--font-size);
color: var(--subtle-text);
}
.date-picker button {
background: var(--accent);
border: none;
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: bold;
color: #ffffff;
transition: background 0.2s ease;
}
.date-picker button:hover {
background: #2B6CB0;
}
.view-selector {
position: absolute;
top: var(--spacing);
right: var(--spacing);
display: flex;
align-items: center;
gap: 8px;
}
.view-selector select {
display: none;
}
.view-selector .toggle-view-btn,
.view-selector .theme-toggle-btn {
background: none;
border: none;
color: var(--text);
font-size: 1.2rem;
padding: 6px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.view-selector .toggle-view-btn:hover,
.view-selector .theme-toggle-btn:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.list-view .day-item {
background: var(--card-bg);
margin-bottom: var(--spacing);
padding: var(--spacing);
border-radius: var(--border-radius);
}
.day-header {
font-size: 1.4rem;
color: var(--accent);
margin-bottom: 8px;
position: relative;
}
.day-header::after {
content: '';
position: absolute;
bottom: -4px;
left: 0;
width: 40px;
height: 3px;
background: var(--accent);
border-radius: 2px;
}
.meal-selection {
margin: 6px 0;
font-size: 1rem;
}
.meal-selection a {
color: var(--text);
text-decoration: none;
}
.meal-selection a:hover {
text-decoration: underline;
}
.not-assigned {
color: var(--faint-text);
font-style: italic;
}
.card-view .card-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: var(--spacing);
}
.meal-card {
position: relative;
height: 200px;
color: #fff;
background-size: cover;
background-position: center;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
display: flex;
align-items: flex-end;
cursor: pointer;
}
.meal-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(49, 130, 206, 0.3), rgba(0, 0, 0, 0.7), transparent);
pointer-events: none;
transition: opacity 0.3s ease;
opacity: 1;
}
.meal-card[data-has-image="true"]::before {
opacity: 0;
}
.card-content {
position: relative;
padding: 12px;
z-index: 1;
width: 100%;
}
.card-content .day {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 6px;
}
.card-content .meal {
font-size: 0.9rem;
line-height: 1.2;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,4 +1,9 @@
angular.module('mealMenuApp', ['ngSanitize'])
function setTheme(mode) {
document.documentElement.setAttribute('data-theme', mode);
localStorage.setItem('theme', mode);
}
angular.module('mealMenuApp', ['ngSanitize'])
.controller('MealMenuController', function ($scope, $http, $sce) {
console.log("Controller initierad");
@@ -17,6 +22,7 @@
.then(res => {
console.log("Måltider hämtade:", res.data);
$scope.meals = res.data;
console.log("Alla måltider med bilder:", $scope.meals);
return res;
})
.catch(err => console.error("Fel vid hämtning av måltider:", err));
@@ -53,8 +59,10 @@
$scope.menu[day][type + 'MealId'] = item[idKey];
$scope.menu[day][type + 'MealName'] = m?.Name || item[nameKey] || 'Okänd rätt';
if (m?.ImageUrl) {
$scope.menu[day].imageUrl = m.ImageUrl;
if (m?.ImageUrl && !$scope.menu[day].imageUrl) {
$scope.menu[day].imageUrl = m.ImageUrl.startsWith('/')
? m.ImageUrl
: '/' + m.ImageUrl;
}
}
});
@@ -71,7 +79,17 @@
$scope.getDayImage = function (day) {
const item = $scope.menu[day];
return (item && item.imageUrl) || '/images/default-meal.jpg';
const img = item?.imageUrl;
if (img && img.startsWith('/')) {
return img;
}
if (img && !img.startsWith('/')) {
return '/' + img; // Fixa ev. saknad inledande snedstreck
}
return '/images/default-meal.jpg';
};
$scope.getMealIdByDay = function (day) {
@@ -106,10 +124,57 @@
const yearStart = new Date(d.getFullYear(), 0, 1);
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
$scope.getViewIcon = function () {
return $scope.viewMode === 'list' ? '🗒️' : '▣';
};
$scope.toggleView = function () {
$scope.viewMode = $scope.viewMode === 'list' ? 'card' : 'list';
// Uppdatera knappens ikon
setTimeout(() => {
const btn = document.getElementById('toggle-view');
if (btn) {
btn.textContent = $scope.getViewIcon();
}
}, 0);
};
console.log("Initierar måltidsladdning...");
$scope.loadMeals().then(() => {
console.log("Laddar meny efter måltider...");
$scope.loadMenu();
setTimeout(() => {
const viewBtn = document.getElementById('toggle-view');
if (viewBtn) viewBtn.textContent = $scope.getViewIcon();
}, 0);
});
});
document.addEventListener("DOMContentLoaded", function () {
const themeBtn = document.getElementById('toggle-theme');
const viewBtn = document.getElementById('toggle-view');
if (themeBtn) {
themeBtn.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const newTheme = current === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
themeBtn.textContent = newTheme === 'dark' ? '🌙' : '☀️';
});
}
const saved = localStorage.getItem('theme');
if (saved) {
setTheme(saved);
} else {
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(systemPrefersDark ? 'dark' : 'light');
}
// Initiera ikon för vy
const scope = angular.element(document.body).scope();
if (viewBtn && scope) {
viewBtn.textContent = scope.viewMode === 'list' ? '🗒️' : '▣';
}
});