grocy/public/viewjs/mealplan.js

467 lines
16 KiB
JavaScript

var firstRender = true;
var firstDay = null;
if (!Grocy.CalendarFirstDayOfWeek.isEmpty())
{
firstDay = parseInt(Grocy.CalendarFirstDayOfWeek);
}
var calendar = $("#calendar").fullCalendar({
"themeSystem": "bootstrap4",
"header": {
"left": "title",
"center": "",
"right": "prev,today,next"
},
"weekNumbers": false,
"eventLimit": true,
"eventSources": fullcalendarEventSources,
"defaultView": ($(window).width() < 768) ? "basicDay" : "basicWeek",
"firstDay": firstDay,
"viewRender": function(view)
{
if (firstRender)
{
firstRender = false
}
else
{
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
}
$(".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 weekRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", weekRecipeName);
var weekCosts = 0;
var weekRecipeOrderMissingButtonHtml = "";
var weekRecipeConsumeButtonHtml = "";
if (weekRecipe !== null)
{
var weekCostsHtml = "";
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
weekCosts = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).costs;
weekCostsHtml = __t("Week costs") + ': <span class="locale-number locale-number-currency">' + weekCosts.toString() + "</span> ";
}
var weekRecipeOrderMissingButtonDisabledClasses = "";
if (FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).need_fulfilled_with_shopping_list == 1)
{
weekRecipeOrderMissingButtonDisabledClasses = "disabled";
}
var weekRecipeConsumeButtonDisabledClasses = "";
if (FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).need_fulfilled == 0)
{
weekRecipeConsumeButtonDisabledClasses = "disabled";
}
weekRecipeOrderMissingButtonHtml = '<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + weekRecipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-cart-plus"></i></a>'
weekRecipeConsumeButtonHtml = '<a class="ml-1 btn btn-outline-success btn-xs recipe-consume-button ' + weekRecipeConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume all ingredients needed by this recipe") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-utensils"></i></a>'
}
$(".fc-header-toolbar .fc-center").html("<h4>" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
},
"eventRender": function(event, element)
{
element.removeClass("fc-event");
element.addClass("text-center");
if (event.type == "recipe")
{
var recipe = JSON.parse(event.recipe);
if (recipe === null || recipe === undefined)
{
return false;
}
var mealPlanEntry = JSON.parse(event.mealPlanEntry);
var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", recipe.id);
element.attr("data-recipe", event.recipe);
element.attr("data-meal-plan-entry", event.mealPlanEntry);
var recipeOrderMissingButtonDisabledClasses = "";
if (resolvedRecipe.need_fulfilled_with_shopping_list == 1)
{
recipeOrderMissingButtonDisabledClasses = "disabled";
}
var recipeConsumeButtonDisabledClasses = "";
if (resolvedRecipe.need_fulfilled == 0)
{
recipeConsumeButtonDisabledClasses = "disabled";
}
var fulfillmentInfoHtml = __t('Enough in stock');
var fulfillmentIconHtml = '<i class="fas fa-check text-success"></i>';
if (resolvedRecipe.need_fulfilled != 1)
{
fulfillmentInfoHtml = __t('Not enough in stock');
var fulfillmentIconHtml = '<i class="fas fa-times text-danger"></i>';
}
var costsAndCaloriesPerServing = ""
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + resolvedRecipe.costs + '</span> / <span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '<h5>';
}
else
{
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '<h5>';
}
element.html('\
<div> \
<h5 class="text-truncate">' + recipe.name + '<h5> \
<h5 class="small text-truncate">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</h5> \
<h5 class="small timeago-contextual text-truncate">' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '</h5> \
' + costsAndCaloriesPerServing + ' \
<h5> \
<a class="ml-1 btn btn-outline-danger btn-xs remove-recipe-button" href="#"><i class="fas fa-trash"></i></a> \
<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + recipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-cart-plus"></i></a> \
<a class="ml-1 btn btn-outline-success btn-xs recipe-consume-button ' + recipeConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume all ingredients needed by this recipe") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-utensils"></i></a> \
<a class="ml-1 btn btn-outline-secondary btn-xs recipe-popup-button" href="#" data-toggle="tooltip" title="' + __t("Display recipe") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-eye"></i></a> \
</h5> \
</div>');
if (recipe.picture_file_name && !recipe.picture_file_name.isEmpty())
{
element.html(element.html() + '<div class="mx-auto"><img data-src="' + U("/api/files/recipepictures/") + btoa(recipe.picture_file_name) + '?force_serve_as=picture&best_fit_width=400" class="img-fluid lazy"></div>')
}
var dayRecipeName = event.start.format("YYYY-MM-DD");
if (!$("#day-summary-" + dayRecipeName).length) // This runs for every event/recipe, so maybe multiple times per day, so only add the day summary once
{
var dayRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", dayRecipeName);
if (dayRecipe != null)
{
var dayRecipeResolved = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", dayRecipe.id);
var costsAndCaloriesPerDay = ""
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 ' + __t('per day') + '<h5>';
}
else
{
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>');
}
}
}
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)
{
RefreshLocaleNumberDisplay();
LoadImagesLazy();
$('[data-toggle="tooltip"]').tooltip();
if (GetUriParam("week") !== undefined)
{
$("#calendar").fullCalendar("gotoDate", GetUriParam("week"));
}
},
});
$(document).on("click", ".add-recipe-button", function(e)
{
var day = $(this).parent().parent().data("date");
$("#add-recipe-modal-title").text(__t("Add recipe to %s", day.toString()));
$("#day").val(day.toString());
Grocy.Components.RecipePicker.Clear();
$("#add-recipe-modal").modal("show");
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)
{
Grocy.Components.RecipePicker.GetInputElement().focus();
})
$("#add-note-modal").on("shown.bs.modal", function (e)
{
$("#note").focus();
})
$(document).on("click", ".remove-recipe-button", function(e)
{
var mealPlanEntry = JSON.parse($(this).parents(".fc-h-event:first").attr("data-meal-plan-entry"));
Grocy.Api.Delete('objects/meal_plan/' + mealPlanEntry.id.toString(), { },
function(result)
{
window.location.reload();
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
});
$(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)
{
e.preventDefault();
if (document.getElementById("add-recipe-form").checkValidity() === false) //There is at least one validation error
{
return false;
}
Grocy.Api.Post('objects/meal_plan', $('#add-recipe-form').serializeJSON(),
function(result)
{
window.location.reload();
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
});
$('#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)
{
if (event.keyCode === 13) //Enter
{
event.preventDefault();
if (document.getElementById("add-recipe-form").checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
$("#save-add-recipe-button").click();
}
}
});
$(document).on("keyodwn", "#servings", function(e)
{
if (event.keyCode === 13) //Enter
{
event.preventDefault();
if (document.getElementById("add-recipe-form").checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
$("#save-add-recipe-button").click();
}
}
});
$(document).on('click', '.recipe-order-missing-button', function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
var button = $(this);
bootbox.confirm({
message: __t('Are you sure to put all missing ingredients for recipe "%s" on the shopping list?', objectName),
closeButton: false,
buttons: {
confirm: {
label: __t('Yes'),
className: 'btn-success'
},
cancel: {
label: __t('No'),
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
Grocy.FrontendHelpers.BeginUiBusy();
Grocy.Api.Post('recipes/' + objectId + '/add-not-fulfilled-products-to-shoppinglist', { },
function(result)
{
if (button.attr("data-recipe-type") == "normal")
{
button.addClass("disabled");
Grocy.FrontendHelpers.EndUiBusy();
}
else
{
window.location.reload();
}
},
function(xhr)
{
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
}
}
});
});
$(document).on('click', '.recipe-consume-button', function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
bootbox.confirm({
message: __t('Are you sure to consume all ingredients needed by recipe "%s" (ingredients marked with "check only if a single unit is in stock" will be ignored)?', objectName),
closeButton: false,
buttons: {
confirm: {
label: __t('Yes'),
className: 'btn-success'
},
cancel: {
label: __t('No'),
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
Grocy.FrontendHelpers.BeginUiBusy();
Grocy.Api.Post('recipes/' + objectId + '/consume', { },
function(result)
{
Grocy.FrontendHelpers.EndUiBusy();
toastr.success(__t('Removed all ingredients of recipe "%s" from stock', objectName));
},
function(xhr)
{
toastr.warning(__t('Not all ingredients of recipe "%s" are in stock, nothing removed', objectName));
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
}
}
});
});
$(document).on("click", ".recipe-popup-button", function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectId = $(e.currentTarget).attr('data-recipe-id');
bootbox.dialog({
message: '<iframe height="650px" class="embed-responsive" src="' + U("/recipes?embedded&recipe=") + objectId + '#fullscreen"></iframe>',
size: 'extra-large',
backdrop: true,
closeButton: false,
buttons: {
cancel: {
label: __t('Close'),
className: 'btn-secondary responsive-button',
callback: function()
{
bootbox.hideAll();
}
}
}
});
});
$(window).on("resize", function()
{
// Automatically switch the calendar to "basicDay" view on small screens
// and to "basicWeek" otherwise
if ($(window).width() < 768)
{
calendar.fullCalendar("changeView", "basicDay");
}
else
{
calendar.fullCalendar("changeView", "basicWeek");
}
});