var firstRender = true; var firstDay = null; if (!Grocy.CalendarFirstDayOfWeek.isEmpty()) { firstDay = parseInt(Grocy.CalendarFirstDayOfWeek); } if (!Grocy.MealPlanFirstDayOfWeek.isEmpty()) { firstDay = parseInt(Grocy.MealPlanFirstDayOfWeek); } 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('\
\ \ \ \
'); 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 = ""; var weekCostsHtml = ""; if (weekRecipe !== null) { if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { weekCosts = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", weekRecipe.id).costs; weekCostsHtml = __t("Week costs") + ': ' + weekCosts.toString() + " "; } 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 || weekCosts == 0) { weekRecipeConsumeButtonDisabledClasses = "disabled"; } weekRecipeOrderMissingButtonHtml = '' weekRecipeConsumeButtonHtml = '' } $(".fc-header-toolbar .fc-center").html("

" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "

"); }, "eventRender": function(event, element) { element.removeClass("fc-event"); element.addClass("text-center"); element.attr("data-meal-plan-entry", event.mealPlanEntry); var mealPlanEntry = JSON.parse(event.mealPlanEntry); if (event.type == "recipe") { var recipe = JSON.parse(event.recipe); if (recipe === null || recipe === undefined) { return false; } var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", recipe.id); element.attr("data-recipe", event.recipe); 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 = ''; if (resolvedRecipe.need_fulfilled != 1) { fulfillmentInfoHtml = __t('Not enough in stock'); var fulfillmentIconHtml = ''; } var costsAndCaloriesPerServing = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { costsAndCaloriesPerServing = '
' + resolvedRecipe.costs + ' / ' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; } else { costsAndCaloriesPerServing = '
' + resolvedRecipe.calories + ' kcal ' + __t('per serving') + '
'; } element.html('\
\
' + recipe.name + '
\
' + __n(mealPlanEntry.recipe_servings, "%s serving", "%s servings") + '
\
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ ' + costsAndCaloriesPerServing + ' \
\ \ \ \ \
\
'); if (recipe.picture_file_name && !recipe.picture_file_name.isEmpty()) { element.html(element.html() + '
') } 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 = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } else { costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } $(".fc-day-header[data-date='" + dayRecipeName + "']").append('
' + costsAndCaloriesPerDay + '
'); } } } if (event.type == "product") { var productDetails = JSON.parse(event.productDetails); if (productDetails === null || productDetails === undefined) { return false; } if (productDetails.last_price === null) { productDetails.last_price = 0; } element.attr("data-product-details", event.productDetails); var productOrderMissingButtonDisabledClasses = "disabled"; if (parseFloat(productDetails.stock_amount_aggregated) < parseFloat(mealPlanEntry.product_amount)) { productOrderMissingButtonDisabledClasses = ""; } var productConsumeButtonDisabledClasses = "disabled"; if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount)) { productConsumeButtonDisabledClasses = ""; } fulfillmentInfoHtml = __t('Not enough in stock'); var fulfillmentIconHtml = ''; if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount)) { var fulfillmentInfoHtml = __t('Enough in stock'); var fulfillmentIconHtml = ''; } var costsAndCaloriesPerServing = "" if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { costsAndCaloriesPerServing = '
' + productDetails.last_price / productDetails.product.qu_factor_purchase_to_stock * mealPlanEntry.product_amount + ' / ' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; } else { costsAndCaloriesPerServing = '
' + productDetails.product.calories * mealPlanEntry.product_amount + ' kcal ' + '
'; } element.html('\
\
' + productDetails.product.name + '
\
' + mealPlanEntry.product_amount + " " + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural) + '
\
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ ' + costsAndCaloriesPerServing + ' \
\ \ \ \
\
'); if (productDetails.product.picture_file_name && !productDetails.product.picture_file_name.isEmpty()) { element.html(element.html() + '
') } 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 = '
' + dayRecipeResolved.costs + ' / ' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } else { costsAndCaloriesPerDay = '
' + dayRecipeResolved.calories + ' kcal ' + __t('per day') + '
'; } $(".fc-day-header[data-date='" + dayRecipeName + "']").append('
' + costsAndCaloriesPerDay + '
'); } } } else if (event.type == "note") { element.html('\
\
' + mealPlanEntry.note + '
\
\ \
\
'); } }, "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"); }); $(document).on("click", ".add-product-button", function(e) { var day = $(this).parent().parent().parent().data("date"); $("#add-product-modal-title").text(__t("Add product to %s", day.toString())); $("#day").val(day.toString()); Grocy.Components.ProductPicker.Clear(); $("#add-product-modal").modal("show"); Grocy.FrontendHelpers.ValidateForm("add-product-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(); }) $("#add-product-modal").on("shown.bs.modal", function (e) { Grocy.Components.ProductPicker.GetInputElement().focus(); }) $(document).on("click", ".remove-recipe-button, .remove-note-button, .remove-product-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) } ); }); $('#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', jsonData, function(result) { window.location.reload(); }, function(xhr) { Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) } ); }); $('#save-add-product-button').on('click', function(e) { e.preventDefault(); if (document.getElementById("add-product-form").checkValidity() === false) //There is at least one validation error { return false; } var jsonData = $('#add-product-form').serializeJSON(); jsonData.day = $("#day").val(); delete jsonData.display_amount; jsonData.product_amount = jsonData.amount; delete jsonData.amount; jsonData.product_qu_id = jsonData.qu_id; delete jsonData.qu_id; Grocy.Api.Post('objects/meal_plan', 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("keydown", "#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', '.product-consume-button', function(e) { e.preventDefault(); // Remove the focus from the current button // to prevent that the tooltip stays until clicked anywhere else document.activeElement.blur(); Grocy.FrontendHelpers.BeginUiBusy(); var productId = $(e.currentTarget).attr('data-product-id'); var consumeAmount = parseFloat($(e.currentTarget).attr('data-product-amount')); Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': false }, function(bookingResponse) { Grocy.Api.Get('stock/products/' + productId, function (result) { var toastMessage = __t('Removed %1$s of %2$s from stock', consumeAmount.toString() + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; Grocy.FrontendHelpers.EndUiBusy(); toastr.success(toastMessage); window.location.reload(); }, function(xhr) { Grocy.FrontendHelpers.EndUiBusy(); console.error(xhr); } ); }, 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: '', 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"); } }); Grocy.Components.ProductPicker.GetPicker().on('change', function(e) { var productId = $(e.target).val(); if (productId) { Grocy.Api.Get('stock/products/' + productId, function(productDetails) { Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id, true); $('#display_amount').val(1); $('#display_amount').focus(); $(".input-group-productamountpicker").trigger("change"); Grocy.FrontendHelpers.ValidateForm('add-product-form'); }, function(xhr) { console.error(xhr); } ); } }); function UndoStockTransaction(transactionId) { Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', { }, function (result) { toastr.success(__t("Transaction successfully undone")); }, function (xhr) { console.error(xhr); } ); };