Ny meals!
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
elias
2025-06-05 23:23:36 +02:00
parent bc77e39c72
commit b286fed88a
7 changed files with 398 additions and 92 deletions

View File

@@ -4,6 +4,8 @@ using Aberwyn.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System.Net.Http.Headers;
using System.Text.Json;
namespace Aberwyn.Controllers
{
@@ -65,5 +67,44 @@ namespace Aberwyn.Controllers
return StatusCode(500, "Failed to add meal.");
}
#region Skolmat
[HttpGet("skolmat")]
public async Task<IActionResult> GetSkolmat(int week, [FromQuery] string sensor = "sensor.engelbrektsskolan")
{
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3M2Q5ODIyYzU4ZWI0MjM4OWEyMGQ2MWQ2MWVhOWYzYyIsImlhdCI6MTc0OTE1MzY1MCwiZXhwIjoyMDY0NTEzNjUwfQ.8C_dKm7P1BbFVJKc_wT76YnQqiZxkP9EzrsLbfD0Ml8";
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.1.196:8123/");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetAsync($"/api/states/{sensor}");
if (!res.IsSuccessStatusCode)
return StatusCode((int)res.StatusCode, $"Kunde inte hämta data för {sensor}");
var json = await res.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var attr = doc.RootElement.GetProperty("attributes");
if (!attr.TryGetProperty("calendar", out var calendar))
return NotFound("Ingen kalender hittades i attributen");
if (!calendar.TryGetProperty(week.ToString(), out var weekData))
return NotFound("Ingen skolmat för vecka " + week);
var result = new List<object>();
foreach (var day in weekData.EnumerateArray())
{
var weekday = day.GetProperty("weekday").GetString();
var date = day.GetProperty("date").GetString();
var courses = day.GetProperty("courses").EnumerateArray().Select(c => c.GetString()).ToList();
result.Add(new { weekday, date, courses });
}
return Ok(result);
}
#endregion
}
}

View File

@@ -1,95 +1,116 @@
@model List<Aberwyn.Controllers.AdminUserViewModel>
<link rel="stylesheet" href="~/css/admin.css" />
<h2>Användarhantering</h2>
<h3>Skapa ny roll</h3>
<form method="post" asp-action="CreateRole">
<div class="form-group">
<input type="text" name="roleName" class="form-control" placeholder="Namn på ny roll" required />
</div>
<button type="submit" class="btn btn-success mt-2">Skapa roll</button>
</form>
<h3>Skapa ny användare</h3>
@if (TempData["Message"] != null)
{
<div class="alert alert-success">@TempData["Message"]</div>
}
<form method="post" asp-action="CreateUser">
<div class="form-group">
<label for="email">E-post:</label>
<input type="email" name="email" class="form-control" required />
</div>
<div class="form-group">
<label for="password">Lösenord:</label>
<input type="password" name="password" class="form-control" required />
</div>
<button type="submit" class="btn btn-success mt-2">Skapa användare</button>
</form>
<hr />
<h4>Alla roller</h4>
<ul>
@foreach (var role in ViewBag.AllRoles as List<string>)
{
<li>@role</li>
}
</ul>
<table class="table">
<thead>
<tr>
<th>E-post</th>
<th>Roller</th>
<th>Lägg till roll</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr>
<td>@user.Email</td>
<td>
@foreach (var role in user.Roles)
{
<span>@role</span>
<form method="post" asp-action="RemoveFromRole" style="display:inline;">
<input type="hidden" name="userId" value="@user.UserId" />
<input type="hidden" name="role" value="@role" />
<button type="submit" class="btn btn-sm btn-danger">Ta bort</button>
</form>
}
</td>
<td>
<form method="post" asp-action="AddToRole" class="form-inline">
<input type="hidden" name="userId" value="@user.UserId" />
<input type="text" name="role" class="form-control" placeholder="Rollnamn" />
<button type="submit" class="btn btn-sm btn-primary">Lägg till</button>
</form>
</td>
</tr>
}
</tbody>
</table>
<button ng-click="generateThumbnails()">Generera thumbnails</button>
<h3>Testa Pushnotis</h3>
<form onsubmit="sendPush(event)">
<div class="form-group">
<label for="title">Titel:</label>
<input type="text" id="title" name="title" class="form-control" required />
</div>
<div class="form-group">
<label for="body">Meddelande:</label>
<input type="text" id="body" name="body" class="form-control" required />
</div>
<button type="submit" class="btn btn-warning mt-2">Skicka testnotis</button>
</form>
<h2 class="mb-3">Adminpanel</h2>
<div class="card mb-4">
<div class="card-header bg-secondary text-white">
Importera från annan databas
<div class="card-header">Roller och användare</div>
<div class="card-body">
<div class="row">
<div>
<h4>Skapa ny roll</h4>
<form method="post" asp-action="CreateRole">
<div class="form-group">
<input type="text" name="roleName" class="form-control" placeholder="Namn på ny roll" required />
</div>
<button type="submit" class="btn btn-success mt-2">Skapa roll</button>
</form>
</div>
<div>
<h4>Skapa ny användare</h4>
@if (TempData["Message"] != null)
{
<div class="alert alert-success">@TempData["Message"]</div>
}
<form method="post" asp-action="CreateUser">
<div class="form-group">
<label for="email">E-post:</label>
<input type="email" name="email" class="form-control" required />
</div>
<div class="form-group">
<label for="password">Lösenord:</label>
<input type="password" name="password" class="form-control" required />
</div>
<button type="submit" class="btn btn-success mt-2">Skapa användare</button>
</form>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">Befintliga roller och användare</div>
<div class="card-body">
<h5>Alla roller</h5>
<ul>
@foreach (var role in ViewBag.AllRoles as List<string>)
{
<li>@role</li>
}
</ul>
<table class="table">
<thead>
<tr>
<th>E-post</th>
<th>Roller</th>
<th>Lägg till roll</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr>
<td>@user.Email</td>
<td>
@foreach (var role in user.Roles)
{
<span>@role</span>
<form method="post" asp-action="RemoveFromRole" style="display:inline;">
<input type="hidden" name="userId" value="@user.UserId" />
<input type="hidden" name="role" value="@role" />
<button type="submit" class="btn btn-sm btn-danger">Ta bort</button>
</form>
}
</td>
<td>
<form method="post" asp-action="AddToRole" class="form-inline">
<input type="hidden" name="userId" value="@user.UserId" />
<input type="text" name="role" class="form-control" placeholder="Rollnamn" />
<button type="submit" class="btn btn-sm btn-primary">Lägg till</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="card mb-4">
<div class="card-header">Övriga funktioner</div>
<div class="card-body">
<button class="btn btn-secondary mb-3" onclick="generateThumbnails()">🖼 Generera thumbnails</button>
<h4>Testa Pushnotis</h4>
<form onsubmit="sendPush(event)">
<div class="form-group">
<label for="title">Titel:</label>
<input type="text" id="title" name="title" class="form-control" required />
</div>
<div class="form-group">
<label for="body">Meddelande:</label>
<input type="text" id="body" name="body" class="form-control" required />
</div>
<button type="submit" class="btn btn-warning mt-2">Skicka testnotis</button>
</form>
</div>
</div>
<div class="card mb-5">
<div class="card-header">Importera från annan databas</div>
<div class="card-body">
<form id="customDbForm">
<div class="form-group">
@@ -124,8 +145,22 @@
<script>
function generateThumbnails() {
if (!confirm("Generera thumbnails för alla måltider som saknar?")) return;
fetch("/admin/GenerateThumbnails", {
method: "POST"
})
.then(res => res.text())
.then(msg => {
alert("✅ " + msg);
})
.catch(err => {
console.error("Fel vid generering av thumbnails:", err);
alert("❌ Ett fel uppstod vid generering av thumbnails.");
});
}
function collectFormData() {
const form = document.getElementById("customDbForm");

View File

@@ -65,7 +65,28 @@
</div>
</div>
</div>
<div class="school-meals card-view">
<h2>Skolmat</h2>
<div ng-repeat="school in schoolMealsBySchool">
<div class="school-meal-title" ng-click="toggleSchoolExpanded(school)">
<span class="chevron" ng-class="{ rotated: school.expanded }">&#x25BC;</span>
{{ school.name }}
</div>
<div class="card-container" ng-show="school.expanded">
<div class="meal-card" ng-repeat="day in school.days">
<div class="card-content school-meal-card-content">
<div class="day">{{ day.weekday }}</div>
<div class="meal" ng-repeat="meal in day.courses">{{ meal }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -53,6 +53,8 @@
@if (User.IsInRole("Admin"))
{
<li><a asp-controller="Admin" asp-action="Index"><i class="fas fa-cog"></i> Adminpanel</a></li>
<li><a asp-controller="Admin" asp-action="Todo"><i class="fas fa-cog"></i> Todo</a></li>
}
</ul>
} else

View File

@@ -0,0 +1,116 @@
/* ================================
ADMIN JUSTERAD LEWEL STIL
================================ */
h2, h3, h4,h5 {
margin-top: 2px;
margin-bottom: 12px;
color: #394B5A;
font-weight: 600;
}
form {
background-color: #ffffff;
padding: 2px 8px;
border-radius: 6px;
margin-bottom: 2px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 2px;
}
.form-control {
width: 100%;
max-width: 360px;
padding: 6px 10px;
border-radius: 4px;
border: 1px solid #ccc;
font-size: 14px;
}
button, .btn {
font-size: 14px;
font-weight: 500;
border-radius: 4px;
padding: 6px 12px;
margin-top: 6px;
}
.btn-sm {
padding: 4px 8px;
font-size: 13px;
}
.alert-success {
background-color: #e6ffed;
color: #256029;
padding: 8px 12px;
border-radius: 4px;
border: 1px solid #b4e2c5;
margin-bottom: 16px;
max-width: 360px;
}
table.table {
width: 100%;
border-collapse: collapse;
background-color: #ffffff;
margin-top: 8px;
border-radius: 6px;
overflow: hidden;
font-size: 14px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
table.table th,
table.table td {
padding: 8px 10px;
text-align: left;
border-bottom: 1px solid #eee;
}
table.table th {
background-color: #f0f4f8;
font-weight: 600;
}
.card {
background-color: #ffffff;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 16px;
}
.card-header {
background-color: #394B5A;
color: #ffffff;
font-weight: 600;
padding: 10px 14px;
font-size: 15px;
}
.card-body {
padding: 5px;
}
#testResult {
font-weight: bold;
font-size: 14px;
margin-top: 6px;
}
ul {
margin-top: 0;
margin-bottom: 16px;
padding-left: 20px;
color: #394B5A;
}
@media (max-width: 768px) {
.form-control, .btn {
width: 100%;
}
}

View File

@@ -165,11 +165,21 @@ h1 {
.card-view .card-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-template-columns: repeat(7, 1fr); /* exakt 7 kolumner */
gap: var(--spacing);
width: 100%;
max-width: 100%;
}
@media (max-width: 1024px) {
.card-view .card-container {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 640px) {
.card-view .card-container {
grid-template-columns: repeat(2, 1fr);
}
}
.meal-card {
position: relative;
height: 160px;
@@ -196,7 +206,6 @@ h1 {
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;
@@ -276,3 +285,38 @@ h1 {
background-color: rgba(0, 0, 0, 0.1);
}
.school-meal-card-content {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 100%;
overflow: hidden;
}
.school-meal-card-content .day {
font-size: 1rem;
font-weight: bold;
margin-bottom: 4px;
flex-shrink: 0; /* Förhindrar att tryckas bort */
}
.school-meal-card-content .meal {
font-size: clamp(0.55rem, 1.0vw, 0.8rem);
line-height: 1.2;
text-align: center;
word-break: break-word;
overflow-y: auto;
max-height: 100%; /* Begränsa höjden */
}
.school-meal-title .chevron {
display: inline-block;
transition: transform 0.3s ease;
transform: rotate(-90deg);
}
.school-meal-title .chevron.rotated {
transform: rotate(0deg);
}

View File

@@ -44,7 +44,54 @@ angular.module('mealMenuApp', ['ngSanitize'])
});
}).catch(err => console.error("Fel vid hämtning av meny:", err));
$scope.loadSchoolMeals(); // Lägg till här
};
$scope.schoolMeals = [];
$scope.schoolMealsBySchool = [];
$scope.schoolSensors = [
{ name: "William Engelbrektsskolan", entity: "sensor.engelbrektsskolan" },
{ name: "Louise - Nyeds skolan", entity: "sensor.nyedsskola" },
{ name: "Ludwig - Skogsgläntan", entity: "sensor.skogsglantan" }
];
$scope.schoolMealsBySchool = [];
$scope.loadSchoolMeals = function () {
const savedExpanded = JSON.parse(localStorage.getItem("expandedSchools") || "{}");
$scope.schoolMealsBySchool = $scope.schoolSensors.map(sensor => ({
name: sensor.name,
entity: sensor.entity,
days: [],
expanded: savedExpanded[sensor.entity] || false
}));
$scope.schoolMealsBySchool.forEach(school => {
$http.get(`/api/mealmenuapi/skolmat?week=${$scope.selectedWeek}&sensor=${school.entity}`)
.then(res => {
school.days = res.data;
})
.catch(() => {
school.days = [];
});
});
};
$scope.toggleSchoolExpanded = function (school) {
school.expanded = !school.expanded;
const expandedState = {};
$scope.schoolMealsBySchool.forEach(s => {
expandedState[s.entity] = s.expanded;
});
localStorage.setItem("expandedSchools", JSON.stringify(expandedState));
};
$scope.openMeal = function (mealId) {
if (!mealId) return;