This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 }">▼</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
116
Aberwyn/wwwroot/css/admin.css
Normal file
116
Aberwyn/wwwroot/css/admin.css
Normal 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%;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user