This commit is contained in:
@@ -94,7 +94,6 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
// ✅ Hämta tidigare måltid och kopiera bilddata
|
||||
var existingMeal = service.GetMealById(meal.Id);
|
||||
if (existingMeal != null)
|
||||
{
|
||||
@@ -103,13 +102,17 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
service.SaveOrUpdateMeal(meal);
|
||||
// 🔁 Här är ändringen – använd metoden som även sparar ingredienser
|
||||
service.SaveOrUpdateMealWithIngredients(meal);
|
||||
|
||||
return RedirectToAction("View", new { id = meal.Id });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult DeleteMeal(int id)
|
||||
{
|
||||
|
||||
@@ -134,6 +134,55 @@ namespace Aberwyn.Data
|
||||
return weeklyMenu;
|
||||
}
|
||||
|
||||
public List<Ingredient> GetIngredientsForMeal(int mealId)
|
||||
{
|
||||
var ingredients = new List<Ingredient>();
|
||||
using var conn = GetConnection();
|
||||
conn.Open();
|
||||
string query = "SELECT Id, MealId, Quantity, Item FROM Ingredients WHERE MealId = @mealId";
|
||||
|
||||
using var cmd = new MySqlCommand(query, conn);
|
||||
cmd.Parameters.AddWithValue("@mealId", mealId);
|
||||
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
ingredients.Add(new Ingredient
|
||||
{
|
||||
Id = reader.GetInt32("Id"),
|
||||
MealId = reader.GetInt32("MealId"),
|
||||
Quantity = reader.GetString("Quantity"),
|
||||
Item = reader.GetString("Item")
|
||||
});
|
||||
}
|
||||
return ingredients;
|
||||
}
|
||||
public void SaveIngredients(int mealId, List<Ingredient> ingredients)
|
||||
{
|
||||
using var conn = GetConnection();
|
||||
conn.Open();
|
||||
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
// Ta bort gamla
|
||||
var deleteCmd = new MySqlCommand("DELETE FROM Ingredients WHERE MealId = @mealId", conn, tx);
|
||||
deleteCmd.Parameters.AddWithValue("@mealId", mealId);
|
||||
deleteCmd.ExecuteNonQuery();
|
||||
|
||||
foreach (var ingredient in ingredients)
|
||||
{
|
||||
var insertCmd = new MySqlCommand(
|
||||
"INSERT INTO Ingredients (MealId, Quantity, Item) VALUES (@mealId, @quantity, @item)",
|
||||
conn, tx);
|
||||
|
||||
insertCmd.Parameters.AddWithValue("@mealId", mealId);
|
||||
insertCmd.Parameters.AddWithValue("@quantity", ingredient.Quantity ?? "");
|
||||
insertCmd.Parameters.AddWithValue("@item", ingredient.Item ?? "");
|
||||
insertCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
}
|
||||
|
||||
public List<Meal> GetMeals()
|
||||
{
|
||||
@@ -205,7 +254,7 @@ namespace Aberwyn.Data
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
string query = @"SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl, ImageData, ImageMimeType FROM Meals WHERE Id = @id";
|
||||
string query = @"SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl, ImageData, ImageMimeType, Instructions FROM Meals WHERE Id = @id";
|
||||
|
||||
using var cmd = new MySqlCommand(query, connection);
|
||||
cmd.Parameters.AddWithValue("@id", id);
|
||||
@@ -224,8 +273,9 @@ namespace Aberwyn.Data
|
||||
ImageUrl = reader.IsDBNull(reader.GetOrdinal("ImageUrl")) ? null : reader.GetString(reader.GetOrdinal("ImageUrl")),
|
||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
|
||||
ImageData = reader.IsDBNull(reader.GetOrdinal("ImageData")) ? null : (byte[])reader["ImageData"],
|
||||
ImageMimeType = reader.IsDBNull(reader.GetOrdinal("ImageMimeType")) ? null : reader.GetString(reader.GetOrdinal("ImageMimeType"))
|
||||
|
||||
ImageMimeType = reader.IsDBNull(reader.GetOrdinal("ImageMimeType")) ? null : reader.GetString(reader.GetOrdinal("ImageMimeType")),
|
||||
Instructions = reader.IsDBNull(reader.GetOrdinal("Instructions")) ? null : reader.GetString(reader.GetOrdinal("Instructions")),
|
||||
Ingredients = GetIngredientsForMeal(id)
|
||||
|
||||
};
|
||||
|
||||
@@ -254,9 +304,9 @@ namespace Aberwyn.Data
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
INSERT INTO Meals
|
||||
(Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl, ImageData, ImageMimeType)
|
||||
(Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl, ImageData, ImageMimeType, Instructions)
|
||||
VALUES
|
||||
(@Name, @Description, @ProteinType, @CarbType, @RecipeUrl, @CreatedAt, @ImageUrl, @ImageData, @ImageMimeType);
|
||||
(@Name, @Description, @ProteinType, @CarbType, @RecipeUrl, @CreatedAt, @ImageUrl, @ImageData, @ImageMimeType, @Instructions);
|
||||
SELECT LAST_INSERT_ID();";
|
||||
}
|
||||
else
|
||||
@@ -270,7 +320,8 @@ namespace Aberwyn.Data
|
||||
RecipeUrl = @RecipeUrl,
|
||||
ImageUrl = @ImageUrl,
|
||||
ImageData = @ImageData,
|
||||
ImageMimeType = @ImageMimeType
|
||||
ImageMimeType = @ImageMimeType,
|
||||
Instructions = @Instructions
|
||||
WHERE Id = @Id";
|
||||
cmd.Parameters.AddWithValue("@Id", meal.Id);
|
||||
}
|
||||
@@ -282,10 +333,9 @@ namespace Aberwyn.Data
|
||||
cmd.Parameters.AddWithValue("@RecipeUrl", meal.RecipeUrl ?? "");
|
||||
cmd.Parameters.AddWithValue("@CreatedAt", meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt);
|
||||
cmd.Parameters.AddWithValue("@ImageUrl", meal.ImageUrl ?? "");
|
||||
|
||||
// ✨ Här är nyckeln
|
||||
cmd.Parameters.AddWithValue("@ImageData", (object?)meal.ImageData ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ImageMimeType", meal.ImageMimeType ?? (object)DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Instructions", meal.Instructions ?? "");
|
||||
|
||||
if (meal.Id == 0)
|
||||
{
|
||||
@@ -298,9 +348,6 @@ namespace Aberwyn.Data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void UpdateWeeklyMenu(MenuViewModel menuData)
|
||||
{
|
||||
if (menuData == null || menuData.WeeklyMenus == null)
|
||||
@@ -413,6 +460,17 @@ namespace Aberwyn.Data
|
||||
_ => throw new System.ArgumentException("Invalid day name")
|
||||
};
|
||||
}
|
||||
public void SaveOrUpdateMealWithIngredients(Meal meal)
|
||||
{
|
||||
// Spara/uppdatera måltid
|
||||
SaveOrUpdateMeal(meal);
|
||||
|
||||
// Om måltiden har ingredienser – spara dem
|
||||
if (meal.Ingredients != null && meal.Ingredients.Count > 0)
|
||||
{
|
||||
SaveIngredients(meal.Id, meal.Ingredients);
|
||||
}
|
||||
}
|
||||
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var results = new List<WeeklyMenu>();
|
||||
|
||||
@@ -46,7 +46,15 @@ namespace Aberwyn.Models
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public byte[] ImageData { get; set; }
|
||||
public string ImageMimeType { get; set; } // t.ex. "image/jpeg"
|
||||
|
||||
public string Instructions { get; set; } // 👈 Tillagningstext
|
||||
public List<Ingredient> Ingredients { get; set; } = new();
|
||||
}
|
||||
public class Ingredient
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int MealId { get; set; }
|
||||
public string Quantity { get; set; }
|
||||
public string Item { get; set; }
|
||||
}
|
||||
|
||||
public class MealDto
|
||||
|
||||
@@ -151,4 +151,8 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const modalImageInput = document.getElementById("modalImageInput");
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@model Aberwyn.Models.Meal
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
|
||||
|
||||
@{
|
||||
ViewData["Title"] = Model.Name;
|
||||
bool isEditing = (bool)(ViewData["IsEditing"] ?? false);
|
||||
@@ -14,13 +17,21 @@
|
||||
imageSrc = "/images/placeholder-meal.jpg";
|
||||
}
|
||||
|
||||
bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef");
|
||||
isEditing = isEditing && isChef;
|
||||
}
|
||||
|
||||
<div class="meal-container">
|
||||
<div class="meal-header">
|
||||
<div id="imageDropArea" class="meal-image-wrapper" title="Klicka för att ladda upp bild">
|
||||
<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>
|
||||
@@ -32,8 +43,6 @@
|
||||
{
|
||||
<form asp-action="SaveMeal" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="Id" value="@Model.Id" />
|
||||
<input type="hidden" name="ExistingImageUrl" value="@Model.ImageUrl" />
|
||||
|
||||
<input type="file" id="ImageFile" name="ImageFile" accept="image/*" style="display: none;" />
|
||||
|
||||
<div class="form-group">
|
||||
@@ -61,6 +70,27 @@
|
||||
<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; 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>
|
||||
<button type="button" class="btn-outline" onclick="addIngredientRow()">+ Lägg till ingrediens</button>
|
||||
</div>
|
||||
</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>
|
||||
@@ -87,100 +117,118 @@
|
||||
}
|
||||
|
||||
<div class="buttons">
|
||||
<button type="button" class="btn-outline" onclick="toggleRecipe()">Visa Tillagning</button>
|
||||
<a class="btn-outline" href="@Url.Action("View", new { id = Model.Id, edit = true })">Redigera</a>
|
||||
@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 id="recipe-section" style="display:none; margin-top:1.5rem;">
|
||||
<h2>Så här gör du</h2>
|
||||
<p class="placeholder">(Tillagningsinstruktioner kommer snart...)</p>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.RecipeUrl))
|
||||
{
|
||||
<p><a href="@Model.RecipeUrl" class="recipe-link" target="_blank">Gå till fullständigt recept</a></p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<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;">
|
||||
<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;">
|
||||
<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>
|
||||
<button onclick="closeModal()" style="margin-top: 1rem;">Stäng</button>
|
||||
<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>
|
||||
function toggleRecipe() {
|
||||
const section = document.getElementById('recipe-section');
|
||||
section.style.display = section.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
function toggleRecipe() {
|
||||
const section = document.getElementById('recipe-section');
|
||||
if (!section) return;
|
||||
|
||||
const imageFileInput = document.getElementById("ImageFile");
|
||||
const imageDropArea = document.getElementById("imageDropArea");
|
||||
const imagePreview = document.getElementById("imagePreview");
|
||||
const imageModal = document.getElementById("imageModal");
|
||||
const dropZone = document.getElementById("dropZone");
|
||||
|
||||
if (imageDropArea && @isEditing.ToString().ToLower()) {
|
||||
imageDropArea.style.cursor = "pointer";
|
||||
imageDropArea.addEventListener("click", () => {
|
||||
imageModal.style.display = "block";
|
||||
});
|
||||
|
||||
function closeModal() {
|
||||
imageModal.style.display = "none";
|
||||
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 => {
|
||||
imagePreview.src = e.target.result;
|
||||
|
||||
document.getElementById("imagePreview").src = e.target.result;
|
||||
const dt = new DataTransfer();
|
||||
dt.items.add(file);
|
||||
imageFileInput.files = dt.files;
|
||||
document.getElementById("ImageFile").files = dt.files;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
dropZone.addEventListener("dragover", e => {
|
||||
document.getElementById('dropZone').addEventListener('dragover', e => {
|
||||
e.preventDefault();
|
||||
dropZone.style.borderColor = "#4A90E2";
|
||||
e.currentTarget.style.borderColor = '#6a0dad';
|
||||
});
|
||||
|
||||
dropZone.addEventListener("dragleave", () => {
|
||||
dropZone.style.borderColor = "#aaa";
|
||||
document.getElementById('dropZone').addEventListener('dragleave', e => {
|
||||
e.currentTarget.style.borderColor = '#aaa';
|
||||
});
|
||||
|
||||
dropZone.addEventListener("drop", e => {
|
||||
document.getElementById('dropZone').addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
dropZone.style.borderColor = "#aaa";
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith("image/")) {
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
handleImage(file);
|
||||
closeModal();
|
||||
closeImageModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("paste", e => {
|
||||
if (imageModal.style.display !== "block") return;
|
||||
for (const item of e.clipboardData.items) {
|
||||
if (item.type.startsWith("image/")) {
|
||||
const file = item.getAsFile();
|
||||
handleImage(file);
|
||||
closeModal();
|
||||
}
|
||||
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>
|
||||
.dragging {
|
||||
outline: 2px dashed #6a0dad;
|
||||
}
|
||||
.meal-image-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
@@ -274,4 +322,18 @@ if (imageDropArea && @isEditing.ToString().ToLower()) {
|
||||
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%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -269,3 +269,4 @@ h1 {
|
||||
.top-buttons button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user