This commit is contained in:
@@ -199,27 +199,48 @@ namespace Aberwyn.Controllers
|
|||||||
|
|
||||||
|
|
||||||
[HttpGet("/meal/lab")]
|
[HttpGet("/meal/lab")]
|
||||||
public IActionResult Lab(int? id)
|
public IActionResult Lab(int? id)
|
||||||
|
{
|
||||||
|
if (id.HasValue)
|
||||||
{
|
{
|
||||||
if (id.HasValue)
|
var entry = _menuService.GetRecipeLabEntryById(id.Value);
|
||||||
{
|
if (entry == null) return NotFound();
|
||||||
var entry = _menuService.GetRecipeLabEntryById(id.Value);
|
|
||||||
if (entry == null) return NotFound();
|
|
||||||
|
|
||||||
return View("Lab", entry);
|
// Hämta versioner först när vi vet att entry finns
|
||||||
}
|
entry.Versions = _menuService.GetLabVersionsForEntry(id.Value);
|
||||||
|
return View("Lab", entry);
|
||||||
// Skapa ett tomt labb-entry för formulär
|
|
||||||
var newEntry = new RecipeLabEntry
|
|
||||||
{
|
|
||||||
Title = "",
|
|
||||||
Inspiration = "",
|
|
||||||
Notes = "",
|
|
||||||
Tags = ""
|
|
||||||
};
|
|
||||||
|
|
||||||
return View("Lab", newEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skapa ett tomt labb-entry för formulär
|
||||||
|
var newEntry = new RecipeLabEntry
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Inspiration = "",
|
||||||
|
Notes = "",
|
||||||
|
Tags = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
return View("Lab", newEntry);
|
||||||
|
}
|
||||||
|
// Lägg till dessa actions i din MealController
|
||||||
|
|
||||||
|
[HttpPost("/meal/lab/save")]
|
||||||
|
public IActionResult SaveLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
if (entry.Id == 0)
|
||||||
|
{
|
||||||
|
// Ny entry
|
||||||
|
entry.CreatedAt = DateTime.Now;
|
||||||
|
_menuService.AddLabEntry(entry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Uppdatera befintlig
|
||||||
|
_menuService.UpdateLabEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction("Lab", new { id = entry.Id });
|
||||||
|
}
|
||||||
[HttpPost("/meal/lab/create")]
|
[HttpPost("/meal/lab/create")]
|
||||||
public IActionResult CreateLabEntry(RecipeLabEntry entry)
|
public IActionResult CreateLabEntry(RecipeLabEntry entry)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var results = new List<WeeklyMenu>();
|
var results = new List<WeeklyMenu>();
|
||||||
@@ -507,6 +507,28 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
|||||||
_context.LabVersionIngredients.AddRange(ingredients);
|
_context.LabVersionIngredients.AddRange(ingredients);
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
}
|
}
|
||||||
|
// Lägg till dessa metoder i din MenuService klass
|
||||||
|
|
||||||
|
public void UpdateLabEntry(RecipeLabEntry entry)
|
||||||
|
{
|
||||||
|
var existing = _context.RecipeLabEntries.Find(entry.Id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Title = entry.Title;
|
||||||
|
existing.Inspiration = entry.Inspiration;
|
||||||
|
existing.Notes = entry.Notes;
|
||||||
|
existing.Tags = entry.Tags;
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecipeLabEntry GetRecipeLabEntryWithIngredients(int id)
|
||||||
|
{
|
||||||
|
return _context.RecipeLabEntries
|
||||||
|
.Include(e => e.Versions)
|
||||||
|
.Include(e => e.Ingredients)
|
||||||
|
.FirstOrDefault(e => e.Id == id);
|
||||||
|
}
|
||||||
public void SaveIngredientsForLabEntry(int labEntryId, List<LabIngredient> ingredients)
|
public void SaveIngredientsForLabEntry(int labEntryId, List<LabIngredient> ingredients)
|
||||||
{
|
{
|
||||||
var existing = _context.LabIngredients
|
var existing = _context.LabIngredients
|
||||||
@@ -522,6 +544,16 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
|||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<RecipeLabVersion> GetLabVersionsForEntry(int entryId)
|
||||||
|
{
|
||||||
|
return _context.RecipeLabVersions
|
||||||
|
.Where(v => v.RecipeLabEntryId == entryId)
|
||||||
|
.Include(v => v.Ingredients)
|
||||||
|
.OrderByDescending(v => v.CreatedAt)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,173 @@
|
|||||||
@model Aberwyn.Models.RecipeLabEntry
|
@model Aberwyn.Models.RecipeLabEntry
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Matlabb";
|
ViewData["Title"] = "Matlabb";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h1>Matlabb: @Model.Title</h1>
|
<link rel="stylesheet" href="/css/lab.css" />
|
||||||
|
|
||||||
<form asp-action="CreateLabEntry" method="post">
|
<div class="lab-container">
|
||||||
<input type="hidden" asp-for="Id" />
|
<!-- Vänster: formulär för labb-entry -->
|
||||||
|
<div class="lab-main">
|
||||||
|
<h2>Matlabb – @(string.IsNullOrEmpty(Model.Title) ? "Ny" : Model.Title)</h2>
|
||||||
|
|
||||||
<div>
|
<form asp-action="SaveLabEntry" method="post">
|
||||||
<label>Titel</label>
|
<input type="hidden" name="Id" value="@Model.Id" />
|
||||||
<input asp-for="Title" class="form-control" />
|
<div class="lab-form-group">
|
||||||
</div>
|
<label>Titel</label>
|
||||||
<div>
|
<input type="text" name="Title" value="@Model.Title" class="lab-input" required />
|
||||||
<label>Kategori</label>
|
|
||||||
<input asp-for="Category" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Inspiration</label>
|
|
||||||
<input asp-for="Inspiration" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Anteckningar</label>
|
|
||||||
<textarea asp-for="Notes" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Taggar</label>
|
|
||||||
<input asp-for="Tags" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary mt-2">Spara info</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<h3>Ingredienser</h3>
|
|
||||||
<form asp-action="SaveLabIngredients" method="post">
|
|
||||||
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
|
||||||
|
|
||||||
<div id="ingredient-list">
|
|
||||||
@for (int i = 0; i < Model.Ingredients.Count; i++)
|
|
||||||
{
|
|
||||||
<div class="d-flex mb-2">
|
|
||||||
<input type="text" name="Ingredients[@i].Quantity" value="@Model.Ingredients[i].Quantity" class="form-control me-1" placeholder="Mängd" />
|
|
||||||
<input type="text" name="Ingredients[@i].Item" value="@Model.Ingredients[i].Item" class="form-control me-1" placeholder="Ingrediens" />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="lab-form-group">
|
||||||
</div>
|
<label>Inspiration</label>
|
||||||
<button type="submit" class="btn btn-success">Spara ingredienser</button>
|
<input type="text" name="Inspiration" value="@Model.Inspiration" class="lab-input" />
|
||||||
</form>
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Anteckningar</label>
|
||||||
|
<textarea name="Notes" class="lab-textarea">@Model.Notes</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Taggar</label>
|
||||||
|
<input type="text" name="Tags" value="@Model.Tags" class="lab-input" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="lab-save-button">Spara labb</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<hr />
|
@if (Model.Id > 0)
|
||||||
<h3>Versioner</h3>
|
|
||||||
<form asp-action="AddLabVersion" method="post">
|
|
||||||
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
|
||||||
|
|
||||||
<div class="mb-2">
|
|
||||||
<label>Versionsnamn</label>
|
|
||||||
<input type="text" name="VersionLabel" value="v@(@Model.Versions.Count + 1)" class="form-control" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-secondary">Skapa version från nuvarande ingredienser</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
@if (Model.Versions.Any())
|
|
||||||
{
|
|
||||||
<div class="mt-4">
|
|
||||||
@foreach (var version in Model.Versions)
|
|
||||||
{
|
{
|
||||||
<div class="card mb-3">
|
<hr />
|
||||||
<div class="card-header">
|
|
||||||
<strong>@version.VersionLabel</strong> <span class="text-muted">(@version.CreatedAt.ToShortDateString())</span>
|
<!-- Ingredienser sektion -->
|
||||||
</div>
|
<h3>Ingredienser</h3>
|
||||||
<div class="card-body">
|
<div id="ingredients-section">
|
||||||
<h5>Ingredienser</h5>
|
<form asp-action="SaveLabIngredients" method="post">
|
||||||
@if (version.Ingredients != null && version.Ingredients.Any())
|
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
||||||
{
|
<div id="ingredients-list">
|
||||||
<ul>
|
@if (Model.Ingredients?.Any() == true)
|
||||||
@foreach (var ing in version.Ingredients)
|
{
|
||||||
|
@for (int i = 0; i < Model.Ingredients.Count; i++)
|
||||||
{
|
{
|
||||||
<li>@ing.Quantity @ing.Item</li>
|
<div class="ingredient-row">
|
||||||
|
<input type="text" name="Ingredients[@i].Quantity" value="@Model.Ingredients[i].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
|
<input type="text" name="Ingredients[@i].Item" value="@Model.Ingredients[i].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</ul>
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
<div class="ingredient-row">
|
||||||
<em>Inga ingredienser kopplade.</em>
|
<input type="text" name="Ingredients[0].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
}
|
<input type="text" name="Ingredients[0].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
<h5 class="mt-3">Instruktioner</h5>
|
</div>
|
||||||
<p>@version.Instructions</p>
|
}
|
||||||
|
</div>
|
||||||
<h5 class="mt-3">Kommentar</h5>
|
<button type="button" onclick="addIngredient()" class="lab-add-button">Lägg till ingrediens</button>
|
||||||
<p>@version.ResultNotes</p>
|
<button type="submit" class="lab-save-button">Spara ingredienser</button>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Ny version sektion -->
|
||||||
|
<h3>Ny version</h3>
|
||||||
|
<form asp-action="AddLabVersion" method="post">
|
||||||
|
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Version-label</label>
|
||||||
|
<input type="text" name="VersionLabel" value="v@((Model.Versions?.Count + 1 ?? 1))" class="lab-input" />
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Instruktioner</label>
|
||||||
|
<textarea name="Instructions" class="lab-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="lab-form-group">
|
||||||
|
<label>Resultat</label>
|
||||||
|
<textarea name="ResultNotes" class="lab-textarea"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="lab-add-button">Lägg till version</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p><em>Spara labbet först för att kunna lägga till ingredienser och versioner.</em></p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
<!-- Höger: versioner -->
|
||||||
|
<div class="lab-versions">
|
||||||
|
<h3>Versioner</h3>
|
||||||
|
@if (Model.Versions?.Any() == true)
|
||||||
|
{
|
||||||
|
<div class="version-scroll">
|
||||||
|
@foreach (var version in Model.Versions.OrderByDescending(v => v.CreatedAt))
|
||||||
|
{
|
||||||
|
<div class="version-card">
|
||||||
|
<h4>@version.VersionLabel</h4>
|
||||||
|
<p><strong>Skapat:</strong> @version.CreatedAt.ToString("yyyy-MM-dd")</p>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(version.Instructions))
|
||||||
|
{
|
||||||
|
<p><strong>Instruktioner:</strong><br />@version.Instructions</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(version.ResultNotes))
|
||||||
|
{
|
||||||
|
<p><strong>Resultat:</strong><br />@version.ResultNotes</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (version.Ingredients?.Any() == true)
|
||||||
|
{
|
||||||
|
<p><strong>Ingredienser:</strong></p>
|
||||||
|
<ul>
|
||||||
|
@foreach (var ing in version.Ingredients)
|
||||||
|
{
|
||||||
|
<li>@ing.Quantity @ing.Item</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Inga tidigare versioner ännu.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ingredientIndex = @(Model.Ingredients?.Count ?? 1);
|
||||||
|
|
||||||
|
function addIngredient() {
|
||||||
|
const list = document.getElementById('ingredients-list');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'ingredient-row';
|
||||||
|
div.innerHTML = `
|
||||||
|
<input type="text" name="Ingredients[${ingredientIndex}].Quantity" placeholder="Mängd" class="lab-input" />
|
||||||
|
<input type="text" name="Ingredients[${ingredientIndex}].Item" placeholder="Ingrediens" class="lab-input" />
|
||||||
|
<button type="button" class="remove-ingredient" onclick="removeIngredient(this)">Ta bort</button>
|
||||||
|
`;
|
||||||
|
list.appendChild(div);
|
||||||
|
ingredientIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeIngredient(button) {
|
||||||
|
const row = button.closest('.ingredient-row');
|
||||||
|
row.remove();
|
||||||
|
reindexIngredients();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reindexIngredients() {
|
||||||
|
const rows = document.querySelectorAll('.ingredient-row');
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
const quantityInput = row.querySelector('input[name*="Quantity"]');
|
||||||
|
const itemInput = row.querySelector('input[name*="Item"]');
|
||||||
|
|
||||||
|
if (quantityInput) quantityInput.name = `Ingredients[${index}].Quantity`;
|
||||||
|
if (itemInput) itemInput.name = `Ingredients[${index}].Item`;
|
||||||
|
});
|
||||||
|
ingredientIndex = rows.length;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
116
Aberwyn/wwwroot/css/lab.css
Normal file
116
Aberwyn/wwwroot/css/lab.css
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
.lab-flex-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section {
|
||||||
|
background: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #1F2C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.ingredients {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section.versions {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-section h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-field {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-field input,
|
||||||
|
.lab-field textarea {
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredient-row input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-version-form {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-version {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-notes {
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-result {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #6a0dad;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #6a0dad;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 12px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #5a0ca0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user