353 lines
14 KiB
Plaintext
353 lines
14 KiB
Plaintext
@model Aberwyn.Models.Meal
|
|
@using Microsoft.AspNetCore.Authorization
|
|
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
|
|
|
|
@{
|
|
ViewData["Title"] = Model.Name;
|
|
bool isEditing = (bool)(ViewData["IsEditing"] ?? false);
|
|
string imageSrc;
|
|
if (Model.ImageData != null && Model.ImageData.Length > 0)
|
|
{
|
|
var base64 = Convert.ToBase64String(Model.ImageData);
|
|
var mime = string.IsNullOrEmpty(Model.ImageMimeType) ? "image/jpeg" : Model.ImageMimeType;
|
|
imageSrc = $"data:{mime};base64,{base64}";
|
|
}
|
|
else
|
|
{
|
|
imageSrc = "/images/fallback.jpg";
|
|
}
|
|
|
|
bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef");
|
|
isEditing = isEditing && isChef;
|
|
}
|
|
<link rel="stylesheet" href="/css/meal.css">
|
|
|
|
<div class="meal-container">
|
|
<div class="meal-header">
|
|
<div class="meal-image-wrapper clickable" title="Bild på rätten">
|
|
<img id="imagePreview" src="@imageSrc" alt="@Model.Name" class="meal-image" />
|
|
</div>
|
|
@if (isEditing)
|
|
{
|
|
<div style="margin-top: 0.5rem;">
|
|
<button type="button" class="btn-outline" onclick="openImageModal()">Byt bild</button>
|
|
</div>
|
|
}
|
|
|
|
<div class="meal-meta">
|
|
<h1 class="meal-title">@Model.Name</h1>
|
|
<p class="description">@Model.Description</p>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@if (isEditing)
|
|
{
|
|
<form asp-action="SaveMeal" method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="Id" value="@Model.Id" />
|
|
<input type="file" id="ImageFile" name="ImageFile" accept="image/*" style="display: none;" />
|
|
|
|
<div class="form-group">
|
|
<label for="Name">Namn</label>
|
|
<input type="text" name="Name" value="@Model.Name" class="form-control" required />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="Description">Beskrivning</label>
|
|
<textarea name="Description" class="form-control">@Model.Description</textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="ProteinType">Protein</label>
|
|
<input type="text" name="ProteinType" value="@Model.ProteinType" class="form-control" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="CarbType">Kolhydrat</label>
|
|
<input type="text" name="CarbType" value="@Model.CarbType" class="form-control" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="MealCategoryId">Kategori</label>
|
|
<select asp-for="MealCategoryId" asp-items="@(new SelectList((List<MealCategory>)ViewBag.Categories, "Id", "Name", Model.MealCategoryId))" class="form-control">
|
|
<option value="">-- Välj kategori --</option>
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
<label for="RecipeUrl">Receptlänk</label>
|
|
<input type="url" name="RecipeUrl" value="@Model.RecipeUrl" class="form-control" />
|
|
</div>
|
|
|
|
<div id="recipe-section">
|
|
<h2>Så här gör du</h2>
|
|
<div class="form-group">
|
|
<label for="Instructions">Tillagningsinstruktioner</label>
|
|
<textarea id="Instructions" name="Instructions">@Html.Raw(Model.Instructions)</textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Ingredienser</label>
|
|
<div id="ingredients-list">
|
|
@for (int i = 0; i < (Model.Ingredients?.Count ?? 0); i++)
|
|
{
|
|
<div class="ingredient-row">
|
|
<input type="text" name="Ingredients[@i].Quantity" placeholder="Mängd" value="@Model.Ingredients[i].Quantity" class="form-control ingredient-qty" />
|
|
<input type="text" name="Ingredients[@i].Item" placeholder="Ingrediens" value="@Model.Ingredients[i].Item" class="form-control ingredient-item" />
|
|
</div>
|
|
}
|
|
</div>
|
|
<div style="margin-top: 0.5rem;">
|
|
<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 class="form-group">
|
|
<label>
|
|
<input type="checkbox" name="IsPublished" value="true" @(Model.IsPublished ? "checked" : "") />
|
|
Publicera recept på receptsidan
|
|
</label>
|
|
</div>
|
|
<div class="buttons">
|
|
<button type="submit" class="btn">Spara</button>
|
|
<button type="submit" formaction="@Url.Action("DeleteMeal", new { id = Model.Id })" formmethod="post" onclick="return confirm('Vill du verkligen ta bort denna måltid?');" class="btn-outline">Ta bort</button>
|
|
<a href="@Url.Action("View", new { id = Model.Id, edit = false })" class="btn-outline">Avbryt</a>
|
|
</div>
|
|
</form>
|
|
}
|
|
else
|
|
{
|
|
<div class="meal-details">
|
|
@if (!string.IsNullOrEmpty(Model.ProteinType))
|
|
{
|
|
<p><span class="label">Protein:</span> @Model.ProteinType</p>
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.CarbType))
|
|
{
|
|
<p><span class="label">Kolhydrat:</span> @Model.CarbType</p>
|
|
}
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(Model.RecipeUrl))
|
|
{
|
|
<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">
|
|
@if (isChef)
|
|
{
|
|
<a class="btn-outline" href="@Url.Action("View", new { id = Model.Id, edit = true })">Redigera</a>
|
|
}
|
|
<button type="button" class="btn-outline" onclick="toggleRecipe()">Recept</button>
|
|
</div>
|
|
|
|
<div id="recipe-section" style="display:none; margin-top:1.5rem;">
|
|
<h2>Så här gör du</h2>
|
|
@if (!string.IsNullOrWhiteSpace(Model.Instructions))
|
|
{
|
|
<div class="instructions-content">
|
|
@Html.Raw(Model.Instructions)
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<p class="placeholder">Ingen instruktion har lagts till ännu.</p>
|
|
}
|
|
|
|
@if (Model.Ingredients != null && Model.Ingredients.Any())
|
|
{
|
|
<h3>Ingredienser</h3>
|
|
<ul>
|
|
@foreach (var ing in Model.Ingredients)
|
|
{
|
|
<li>@ing.Quantity @ing.Item</li>
|
|
}
|
|
</ul>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div id="imageModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:1000;">
|
|
<div style="background:white; margin:5% auto; padding:2rem; max-width:400px; border-radius:8px; text-align:center; position:relative;">
|
|
<h3>Klistra in eller dra in bild</h3>
|
|
<div id="dropZone" style="border:2px dashed #aaa; padding:2rem; cursor:pointer;">
|
|
<p>Släpp bild här eller klistra in (Ctrl+V)</p>
|
|
</div>
|
|
<input type="file" id="modalImageInput" accept="image/*" style="display:none;" />
|
|
<button type="button" onclick="document.getElementById('modalImageInput').click()">Välj bild från enhet</button>
|
|
<button onclick="closeImageModal()" style="margin-top:1rem;">Stäng</button>
|
|
</div>
|
|
</div>
|
|
<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/trumbowyg@2.25.1/dist/trumbowyg.min.js"></script>
|
|
<script src="https://kit.fontawesome.com/yourkit.js" crossorigin="anonymous"></script>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
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 div = document.createElement('div');
|
|
div.className = 'ingredient-row';
|
|
|
|
const qtyInput = document.createElement('input');
|
|
qtyInput.type = 'text';
|
|
qtyInput.name = `Ingredients[${index}].Quantity`;
|
|
qtyInput.placeholder = 'Mängd';
|
|
qtyInput.className = 'form-control ingredient-qty';
|
|
qtyInput.value = quantity;
|
|
|
|
const itemInput = document.createElement('input');
|
|
itemInput.type = 'text';
|
|
itemInput.name = `Ingredients[${index}].Item`;
|
|
itemInput.placeholder = 'Ingrediens';
|
|
itemInput.className = 'form-control ingredient-item';
|
|
itemInput.value = item;
|
|
|
|
div.appendChild(qtyInput);
|
|
div.appendChild(itemInput);
|
|
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() {
|
|
const section = document.getElementById('recipe-section');
|
|
if (!section) return;
|
|
|
|
if (section.style.display === 'none' || section.style.display === '') {
|
|
section.style.display = 'block';
|
|
} else {
|
|
section.style.display = 'none';
|
|
}
|
|
}
|
|
function openImageModal() {
|
|
document.getElementById('imageModal').style.display = 'block';
|
|
}
|
|
function closeImageModal() {
|
|
document.getElementById('imageModal').style.display = 'none';
|
|
}
|
|
function handleImage(file) {
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
document.getElementById("imagePreview").src = e.target.result;
|
|
const dt = new DataTransfer();
|
|
dt.items.add(file);
|
|
document.getElementById("ImageFile").files = dt.files;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
document.getElementById('dropZone').addEventListener('dragover', e => {
|
|
e.preventDefault();
|
|
e.currentTarget.style.borderColor = '#6a0dad';
|
|
});
|
|
document.getElementById('dropZone').addEventListener('dragleave', e => {
|
|
e.currentTarget.style.borderColor = '#aaa';
|
|
});
|
|
document.getElementById('dropZone').addEventListener('drop', e => {
|
|
e.preventDefault();
|
|
const file = e.dataTransfer.files[0];
|
|
if (file && file.type.startsWith('image/')) {
|
|
handleImage(file);
|
|
closeImageModal();
|
|
}
|
|
});
|
|
document.getElementById('modalImageInput').addEventListener('change', e => {
|
|
const file = e.target.files[0];
|
|
if (file && file.type.startsWith('image/')) {
|
|
handleImage(file);
|
|
closeImageModal();
|
|
}
|
|
});
|
|
$(document).ready(function () {
|
|
if ($('#Instructions').length > 0) {
|
|
$('#Instructions').trumbowyg({
|
|
autogrow: true,
|
|
btns: [['strong', 'em'], ['unorderedList', 'orderedList'], ['link'], ['removeformat']]
|
|
});
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
|
|
</style>
|