mirror of
https://github.com/grocy/grocy.git
synced 2025-04-30 01:55:47 +00:00
Make it possible to add notes to meal plan (days) (closes #477)
This commit is contained in:
parent
594dc0858b
commit
8d7f985b59
@ -25,6 +25,7 @@
|
|||||||
- When adding or editing a recipe ingredient, a dialog is now used instead of switching between pages (thanks @kriddles)
|
- When adding or editing a recipe ingredient, a dialog is now used instead of switching between pages (thanks @kriddles)
|
||||||
|
|
||||||
### Meal plan improvements/fixes
|
### Meal plan improvements/fixes
|
||||||
|
- It's now possible to add notes per day (in the dropdown of the add button in the header of each day column)
|
||||||
- Added that the calories per serving are now also shown
|
- Added that the calories per serving are now also shown
|
||||||
- Added that the total costs and calories per day are displayed in the header of each day column
|
- Added that the total costs and calories per day are displayed in the header of each day column
|
||||||
- Fixed that when `FEATURE_FLAG_STOCK_PRICE_TRACKING` was set to `false`, prices were still shown (thanks @kriddles)
|
- Fixed that when `FEATURE_FLAG_STOCK_PRICE_TRACKING` was set to `false`, prices were still shown (thanks @kriddles)
|
||||||
|
@ -132,8 +132,20 @@ class RecipesController extends BaseController
|
|||||||
public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
$recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
||||||
|
|
||||||
$events = array();
|
$events = array();
|
||||||
|
|
||||||
|
foreach($this->Database->meal_plan_notes() as $mealPlanNote)
|
||||||
|
{
|
||||||
|
$events[] = array(
|
||||||
|
'id' => intval($mealPlanNote['id']) * -1,
|
||||||
|
'title' => '',
|
||||||
|
'start' => $mealPlanNote['day'],
|
||||||
|
'date_format' => 'date',
|
||||||
|
'note' => json_encode($mealPlanNote),
|
||||||
|
'type' => 'note'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($this->Database->meal_plan() as $mealPlanEntry)
|
foreach($this->Database->meal_plan() as $mealPlanEntry)
|
||||||
{
|
{
|
||||||
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
|
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
|
||||||
@ -149,7 +161,8 @@ class RecipesController extends BaseController
|
|||||||
'start' => $mealPlanEntry['day'],
|
'start' => $mealPlanEntry['day'],
|
||||||
'date_format' => 'date',
|
'date_format' => 'date',
|
||||||
'recipe' => json_encode($recipe),
|
'recipe' => json_encode($recipe),
|
||||||
'mealPlanEntry' => json_encode($mealPlanEntry)
|
'mealPlanEntry' => json_encode($mealPlanEntry),
|
||||||
|
'type' => 'recipe'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3272,7 +3272,8 @@
|
|||||||
"userfields",
|
"userfields",
|
||||||
"userentities",
|
"userentities",
|
||||||
"userobjects",
|
"userobjects",
|
||||||
"meal_plan"
|
"meal_plan",
|
||||||
|
"meal_plan_notes"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ExposedEntityButNoListing": {
|
"ExposedEntityButNoListing": {
|
||||||
|
@ -1633,3 +1633,12 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Are you sure to delete API key \"%s\"?"
|
msgid "Are you sure to delete API key \"%s\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Add note"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Add note to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "per day"
|
||||||
|
msgstr ""
|
||||||
|
8
migrations/0096.sql
Normal file
8
migrations/0096.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE meal_plan_notes (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
day DATE NOT NULL,
|
||||||
|
note TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||||
|
|
||||||
|
UNIQUE(day)
|
||||||
|
);
|
@ -29,7 +29,14 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".fc-day-header").prepend('<a class="mr-1 btn btn-outline-dark btn-xs my-1 add-recipe-button" href="#"><i class="fas fa-plus"></i></a>');
|
$(".fc-day-header").prepend('\
|
||||||
|
<div class="btn-group mr-2 my-1"> \
|
||||||
|
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button""><i class="fas fa-plus"></i></a></button> \
|
||||||
|
<button type="button" class="btn btn-outline-dark btn-xs dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button> \
|
||||||
|
<div class="dropdown-menu"> \
|
||||||
|
<a class="dropdown-item add-note-button" href="#">' + __t('Add note') + '</a> \
|
||||||
|
</div> \
|
||||||
|
</div>');
|
||||||
|
|
||||||
var weekRecipeName = view.start.year().toString() + "-" + ((view.start.week() - 1).toString().padStart(2, "0")).toString();
|
var weekRecipeName = view.start.year().toString() + "-" + ((view.start.week() - 1).toString().padStart(2, "0")).toString();
|
||||||
var weekRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", weekRecipeName);
|
var weekRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", weekRecipeName);
|
||||||
@ -62,6 +69,11 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
$(".fc-header-toolbar .fc-center").html("<h4>" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
|
$(".fc-header-toolbar .fc-center").html("<h4>" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
|
||||||
},
|
},
|
||||||
"eventRender": function(event, element)
|
"eventRender": function(event, element)
|
||||||
|
{
|
||||||
|
element.removeClass("fc-event");
|
||||||
|
element.addClass("text-center");
|
||||||
|
|
||||||
|
if (event.type == "recipe")
|
||||||
{
|
{
|
||||||
var recipe = JSON.parse(event.recipe);
|
var recipe = JSON.parse(event.recipe);
|
||||||
if (recipe === null || recipe === undefined)
|
if (recipe === null || recipe === undefined)
|
||||||
@ -72,9 +84,6 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
var mealPlanEntry = JSON.parse(event.mealPlanEntry);
|
var mealPlanEntry = JSON.parse(event.mealPlanEntry);
|
||||||
var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", recipe.id);
|
var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", recipe.id);
|
||||||
|
|
||||||
element.removeClass("fc-event");
|
|
||||||
element.addClass("text-center");
|
|
||||||
|
|
||||||
element.attr("data-recipe", event.recipe);
|
element.attr("data-recipe", event.recipe);
|
||||||
element.attr("data-meal-plan-entry", event.mealPlanEntry);
|
element.attr("data-meal-plan-entry", event.mealPlanEntry);
|
||||||
|
|
||||||
@ -107,7 +116,7 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '<h5>';
|
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '<h5>';
|
||||||
}
|
}
|
||||||
|
|
||||||
element.html(' \
|
element.html('\
|
||||||
<div> \
|
<div> \
|
||||||
<h5 class="text-truncate">' + recipe.name + '<h5> \
|
<h5 class="text-truncate">' + recipe.name + '<h5> \
|
||||||
<h5 class="small text-truncate">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</h5> \
|
<h5 class="small text-truncate">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</h5> \
|
||||||
@ -137,15 +146,30 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
var costsAndCaloriesPerDay = ""
|
var costsAndCaloriesPerDay = ""
|
||||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||||
{
|
{
|
||||||
costsAndCaloriesPerDay = '<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + dayRecipeResolved.costs + '</span> / <span class="locale-number locale-number-generic">' + dayRecipeResolved.calories + '</span> kcal ' + '<h5>';
|
costsAndCaloriesPerDay = '<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + dayRecipeResolved.costs + '</span> / <span class="locale-number locale-number-generic">' + dayRecipeResolved.calories + '</span> kcal ' + __t('per day') + '<h5>';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
costsAndCaloriesPerDay = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + dayRecipeResolved.calories + '</span> kcal ' + '<h5>';
|
costsAndCaloriesPerDay = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + dayRecipeResolved.calories + '</span> kcal ' + __t('per day') + '<h5>';
|
||||||
}
|
}
|
||||||
$(".fc-day-header[data-date='" + dayRecipeName + "']").append('<h5 id="day-summary-' + dayRecipeName + '" class="small text-truncate border-top pt-1 pb-0">' + costsAndCaloriesPerDay + '</h5>');
|
$(".fc-day-header[data-date='" + dayRecipeName + "']").append('<h5 id="day-summary-' + dayRecipeName + '" class="small text-truncate border-top pt-1 pb-0">' + costsAndCaloriesPerDay + '</h5>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == "note")
|
||||||
|
{
|
||||||
|
var note = JSON.parse(event.note);
|
||||||
|
|
||||||
|
element.attr("data-note", event.note);
|
||||||
|
|
||||||
|
element.html('\
|
||||||
|
<div> \
|
||||||
|
<h5 class="text-truncate">' + note.note + '<h5> \
|
||||||
|
<h5> \
|
||||||
|
<a class="ml-1 btn btn-outline-danger btn-xs remove-note-button" href="#"><i class="fas fa-trash"></i></a> \
|
||||||
|
</h5> \
|
||||||
|
</div>');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"eventAfterAllRender": function(view)
|
"eventAfterAllRender": function(view)
|
||||||
{
|
{
|
||||||
@ -162,7 +186,7 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
|
|
||||||
$(document).on("click", ".add-recipe-button", function(e)
|
$(document).on("click", ".add-recipe-button", function(e)
|
||||||
{
|
{
|
||||||
var day = $(this).parent().data("date");
|
var day = $(this).parent().parent().data("date");
|
||||||
|
|
||||||
$("#add-recipe-modal-title").text(__t("Add recipe to %s", day.toString()));
|
$("#add-recipe-modal-title").text(__t("Add recipe to %s", day.toString()));
|
||||||
$("#day").val(day.toString());
|
$("#day").val(day.toString());
|
||||||
@ -171,11 +195,27 @@ $(document).on("click", ".add-recipe-button", function(e)
|
|||||||
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".add-note-button", function(e)
|
||||||
|
{
|
||||||
|
var day = $(this).parent().parent().parent().data("date");
|
||||||
|
|
||||||
|
$("#add-note-modal-title").text(__t("Add note to %s", day.toString()));
|
||||||
|
$("#day").val(day.toString());
|
||||||
|
$("#note").val("");
|
||||||
|
$("#add-note-modal").modal("show");
|
||||||
|
Grocy.FrontendHelpers.ValidateForm("add-note-form");
|
||||||
|
});
|
||||||
|
|
||||||
$("#add-recipe-modal").on("shown.bs.modal", function(e)
|
$("#add-recipe-modal").on("shown.bs.modal", function(e)
|
||||||
{
|
{
|
||||||
Grocy.Components.RecipePicker.GetInputElement().focus();
|
Grocy.Components.RecipePicker.GetInputElement().focus();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$("#add-note-modal").on("shown.bs.modal", function (e)
|
||||||
|
{
|
||||||
|
$("#note").focus();
|
||||||
|
})
|
||||||
|
|
||||||
$(document).on("click", ".remove-recipe-button", function(e)
|
$(document).on("click", ".remove-recipe-button", function(e)
|
||||||
{
|
{
|
||||||
var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry"));
|
var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry"));
|
||||||
@ -192,6 +232,22 @@ $(document).on("click", ".remove-recipe-button", function(e)
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".remove-note-button", function(e)
|
||||||
|
{
|
||||||
|
var note = JSON.parse($(this).parents(".fc-h-event:first").attr("data-note"));
|
||||||
|
|
||||||
|
Grocy.Api.Delete('objects/meal_plan_notes/' + note.id.toString(), { },
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$('#save-add-recipe-button').on('click', function(e)
|
$('#save-add-recipe-button').on('click', function(e)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -213,6 +269,29 @@ $('#save-add-recipe-button').on('click', function(e)
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#save-add-note-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById("add-note-form").checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData = $('#add-note-form').serializeJSON();
|
||||||
|
jsonData.day = $("#day").val();
|
||||||
|
Grocy.Api.Post('objects/meal_plan_notes', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Grocy.Components.RecipePicker.GetInputElement().keydown(function(event)
|
Grocy.Components.RecipePicker.GetInputElement().keydown(function(event)
|
||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<div class="modal fade" id="add-recipe-modal" tabindex="-1">
|
<div class="modal fade" id="add-recipe-modal" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content text-center">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 id="add-recipe-modal-title" class="modal-title w-100"></h4>
|
<h4 id="add-recipe-modal-title" class="modal-title w-100"></h4>
|
||||||
</div>
|
</div>
|
||||||
@ -68,4 +68,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="add-note-modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 id="add-note-modal-title" class="modal-title w-100"></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="add-note-form" novalidate>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="note">{{ $__t('Note') }}</label>
|
||||||
|
<textarea class="form-control" rows="2" id="note" name="note"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
|
||||||
|
<button id="save-add-note-button" data-dismiss="modal" class="btn btn-success">{{ $__t('Save') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
Loading…
x
Reference in New Issue
Block a user