Meal rating and some fixes to meals
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:
@@ -28,10 +28,10 @@ namespace Aberwyn.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("getPublishedMeals")]
|
[HttpGet("getPublishedMeals")]
|
||||||
public IActionResult GetPublishedMeals()
|
public IActionResult GetPublishedMeals([FromQuery] bool includeUnpublished = false)
|
||||||
{
|
{
|
||||||
var meals = _menuService.GetMeals()
|
var meals = _menuService.GetMeals()
|
||||||
.Where(m => m.IsPublished) // 🟢 filtrera här!
|
.Where(m => includeUnpublished || m.IsPublished)
|
||||||
.Select(m => new {
|
.Select(m => new {
|
||||||
m.Id,
|
m.Id,
|
||||||
m.Name,
|
m.Name,
|
||||||
@@ -44,6 +44,7 @@ namespace Aberwyn.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("getMeals")]
|
[HttpGet("getMeals")]
|
||||||
public IActionResult GetMeals()
|
public IActionResult GetMeals()
|
||||||
{
|
{
|
||||||
|
|||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ namespace Aberwyn.Data
|
|||||||
public DbSet<RecipeLabVersion> RecipeLabVersions { get; set; }
|
public DbSet<RecipeLabVersion> RecipeLabVersions { get; set; }
|
||||||
public DbSet<LabIngredient> LabIngredients { get; set; }
|
public DbSet<LabIngredient> LabIngredients { get; set; }
|
||||||
public DbSet<LabVersionIngredient> LabVersionIngredients { get; set; }
|
public DbSet<LabVersionIngredient> LabVersionIngredients { get; set; }
|
||||||
|
public DbSet<MealRating> MealRatings { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -413,6 +413,32 @@ namespace Aberwyn.Migrations
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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")
|
||||||
@@ -868,6 +894,17 @@ namespace Aberwyn.Migrations
|
|||||||
b.Navigation("Category");
|
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")
|
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
|
||||||
|
|||||||
@@ -177,5 +177,22 @@ public class WeeklyMenu
|
|||||||
public List<Meal> Meals { get; set; } = new();
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,44 +19,51 @@
|
|||||||
{
|
{
|
||||||
<div class="meal-lines">
|
<div class="meal-lines">
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) {
|
@if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) {
|
||||||
<div class="meal-line">
|
<div class="meal-line">
|
||||||
@if (Model.BreakfastThumbnail != null)
|
<p><strong>Frukost:</strong>
|
||||||
{
|
<a href="/meal/view/@Model.BreakfastMealId">@Model.BreakfastMealName</a>
|
||||||
var b64 = Convert.ToBase64String(Model.BreakfastThumbnail);
|
</p>
|
||||||
<button type="button" class="thumb-button" onclick="showLargeImage('@b64', '@Model.BreakfastMealName')">
|
@if (Model.BreakfastThumbnail != null)
|
||||||
<img class="meal-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.BreakfastMealName" />
|
{
|
||||||
</button>
|
var b64 = Convert.ToBase64String(Model.BreakfastThumbnail);
|
||||||
}
|
<div class="meal-large-thumb-container">
|
||||||
<p><strong>Frukost:</strong> @Model.BreakfastMealName</p>
|
<img class="meal-large-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.BreakfastMealName" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) {
|
@if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) {
|
||||||
<div class="meal-line">
|
<div class="meal-line">
|
||||||
@if (Model.LunchThumbnail != null)
|
<p><strong>Lunch:</strong>
|
||||||
{
|
<a href="/meal/view/@Model.LunchMealId">@Model.LunchMealName</a>
|
||||||
var b64 = Convert.ToBase64String(Model.LunchThumbnail);
|
</p>
|
||||||
<button type="button" class="thumb-button" onclick="showLargeImage('@b64', '@Model.LunchMealName')">
|
@if (Model.LunchThumbnail != null)
|
||||||
<img class="meal-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.LunchMealName" />
|
{
|
||||||
</button>
|
var b64 = Convert.ToBase64String(Model.LunchThumbnail);
|
||||||
}
|
<div class="meal-large-thumb-container">
|
||||||
<p><strong>Lunch:</strong> @Model.LunchMealName</p>
|
<img class="meal-large-thumb" src="data:image/jpeg;base64,@b64" alt="@Model.LunchMealName" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 (!string.IsNullOrWhiteSpace(Model.DinnerMealName)) {
|
|
||||||
<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)
|
@if (ViewBag.RestaurantIsOpen as bool? == true)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,7 +13,7 @@
|
|||||||
<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> <!-- om du använder fontawesome -->
|
<script src="https://kit.fontawesome.com/yourkit.js" crossorigin="anonymous"></script>
|
||||||
<style>
|
<style>
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
body { overflow-x: hidden; }
|
body { overflow-x: hidden; }
|
||||||
@@ -16,10 +23,22 @@
|
|||||||
<div class="meal-gallery-container">
|
<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>
|
|
||||||
|
@if (isChef)
|
||||||
|
{
|
||||||
|
<label class="toggle-published">
|
||||||
|
<input type="checkbox" ng-model="includeUnpublished" ng-change="reloadMeals()" />
|
||||||
|
<span>Visa alla</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<a href="/meal/view?edit=true" class="btn-create-meal">+ Ny rätt</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="meal-gallery-grid">
|
<div class="meal-gallery-grid">
|
||||||
@@ -37,29 +56,34 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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.visibleCount = 12;
|
||||||
|
$scope.includeUnpublished = false;
|
||||||
|
|
||||||
$http.get("/api/mealMenuApi/getPublishedMeals").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;
|
||||||
// Lazy loading on scroll
|
|
||||||
window.addEventListener('scroll', function () {
|
|
||||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 150) {
|
|
||||||
$scope.$applyAsync(() => {
|
|
||||||
$scope.visibleCount += 8;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show all when searching
|
|
||||||
$scope.$watch('search', function (newVal) {
|
|
||||||
$scope.visibleCount = newVal ? 9999 : 12;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$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>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
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">
|
||||||
@@ -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)
|
||||||
@@ -96,8 +98,13 @@
|
|||||||
</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>
|
||||||
|
<textarea id="bulkIngredients" placeholder="1 dl mjölk 2 tsk socker" class="form-control" rows="3"></textarea>
|
||||||
|
<button type="button" class="btn-outline" onclick="parseBulkIngredients()">Lägg till från lista</button>
|
||||||
|
<button type="button" class="btn-outline" onclick="addIngredientRow()">+ Lägg till rad</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
@@ -129,6 +136,18 @@
|
|||||||
{
|
{
|
||||||
<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 class="buttons">
|
||||||
@if (isChef)
|
@if (isChef)
|
||||||
@@ -179,11 +198,47 @@
|
|||||||
<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>
|
||||||
function addIngredientRow() {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const list = document.getElementById('ingredients-list');
|
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 index = list.children.length;
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@@ -194,17 +249,44 @@
|
|||||||
qtyInput.name = `Ingredients[${index}].Quantity`;
|
qtyInput.name = `Ingredients[${index}].Quantity`;
|
||||||
qtyInput.placeholder = 'Mängd';
|
qtyInput.placeholder = 'Mängd';
|
||||||
qtyInput.className = 'form-control ingredient-qty';
|
qtyInput.className = 'form-control ingredient-qty';
|
||||||
|
qtyInput.value = quantity;
|
||||||
|
|
||||||
const itemInput = document.createElement('input');
|
const itemInput = document.createElement('input');
|
||||||
itemInput.type = 'text';
|
itemInput.type = 'text';
|
||||||
itemInput.name = `Ingredients[${index}].Item`;
|
itemInput.name = `Ingredients[${index}].Item`;
|
||||||
itemInput.placeholder = 'Ingrediens';
|
itemInput.placeholder = 'Ingrediens';
|
||||||
itemInput.className = 'form-control ingredient-item';
|
itemInput.className = 'form-control ingredient-item';
|
||||||
|
itemInput.value = item;
|
||||||
|
|
||||||
div.appendChild(qtyInput);
|
div.appendChild(qtyInput);
|
||||||
div.appendChild(itemInput);
|
div.appendChild(itemInput);
|
||||||
list.appendChild(div);
|
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;
|
||||||
@@ -265,166 +347,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>
|
||||||
|
|||||||
@@ -46,16 +46,51 @@
|
|||||||
margin-bottom: 28px;
|
margin-bottom: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-lines p {
|
.meal-line {
|
||||||
font-size: 1.2rem;
|
margin-bottom: 0rem;
|
||||||
margin: 8px 0;
|
text-align: center;
|
||||||
color: #0f172a;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-lines strong {
|
.meal-line p {
|
||||||
color: #475569;
|
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 {
|
.no-menu {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
|
|||||||
@@ -24,30 +24,51 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sökfält + checkbox */
|
||||||
.search-container {
|
.search-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 600px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
|
display: flex;
|
||||||
|
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 input {
|
.search-container input[type="text"] {
|
||||||
width: 100%;
|
flex-grow: 1;
|
||||||
padding: 8px 34px 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 20px;
|
border-radius: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container i {
|
.search-container i {
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 0.9rem;
|
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 === */
|
/* === Grid layout === */
|
||||||
@@ -120,22 +141,29 @@ body {
|
|||||||
/* === Mobilanpassning === */
|
/* === Mobilanpassning === */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.meal-gallery-grid {
|
.meal-gallery-grid {
|
||||||
grid-template-columns: repeat(2, 1fr); /* exakt 2 per rad */
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-gallery-header h1 {
|
.meal-gallery-header h1 {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 0.75rem;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container input {
|
.search-container input[type="text"] {
|
||||||
padding: 6px 30px 6px 10px;
|
font-size: 0.95rem;
|
||||||
font-size: 0.9rem;
|
|
||||||
border-radius: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-published {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.meal-card img {
|
.meal-card img {
|
||||||
height: 130px;
|
height: 130px;
|
||||||
}
|
}
|
||||||
@@ -157,3 +185,29 @@ body {
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-create-meal {
|
||||||
|
background-color: #3399ff;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create-meal:hover {
|
||||||
|
background-color: #2389e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.btn-create-meal {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
199
Aberwyn/wwwroot/css/meal.css
Normal file
199
Aberwyn/wwwroot/css/meal.css
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
@@ -429,3 +429,39 @@ body {
|
|||||||
background-color: #f0f0f0;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user