From 57233dba1a7057c2c9a34705373f4b8dd643ca9d Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Mon, 6 May 2019 19:38:47 +0200 Subject: [PATCH] Added first basic version of meal planning (references #146) --- changelog/47_UNRELEASED_xxxx-xx-xx.md | 2 + controllers/RecipesController.php | 23 ++++++ grocy.openapi.json | 3 +- localization/strings.pot | 6 ++ migrations/0070.sql | 8 +++ public/css/grocy.css | 9 ++- public/viewjs/mealplan.js | 100 ++++++++++++++++++++++++++ routes.php | 1 + services/DemoDataGeneratorService.php | 16 +++++ views/layout/default.blade.php | 6 ++ views/mealplan.blade.php | 60 ++++++++++++++++ 11 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 migrations/0070.sql create mode 100644 public/viewjs/mealplan.js create mode 100644 views/mealplan.blade.php diff --git a/changelog/47_UNRELEASED_xxxx-xx-xx.md b/changelog/47_UNRELEASED_xxxx-xx-xx.md index e1f542df..391d86bf 100644 --- a/changelog/47_UNRELEASED_xxxx-xx-xx.md +++ b/changelog/47_UNRELEASED_xxxx-xx-xx.md @@ -3,6 +3,8 @@ - Userfields can have types (Text, Number, Date, etc.) - Will be shown / can be filled on the edit page of the corresponding entity and will also optionally show in the corresponding tables (inclcudes overview pages) - => Can be configured under Master data / Userfields +- New feature: Meal planning + - Simple approach for the beginning (more to come): A week view where you can add recipes for each day (new menu entry in the sidebar, below calendar) - General improvements - The "expires soon" or "due soon" days (yelllow bar at the top of each overview page) can now be configured - => New settings page for each area under the settings icon at the top right diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index b04ad208..abaf2166 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -115,4 +115,27 @@ class RecipesController extends BaseController ]); } } + + public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + $recipes = $this->Database->recipes()->fetchAll(); + + $events = array(); + foreach($this->Database->meal_plan() as $mealPlanEntry) + { + $events[] = array( + 'id' => $mealPlanEntry['id'], + 'title' => FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id'])->name, + 'start' => $mealPlanEntry['day'], + 'date_format' => 'date', + 'recipe' => json_encode(FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id'])), + 'mealPlanEntry' => json_encode($mealPlanEntry) + ); + } + + return $this->AppContainer->view->render($response, 'mealplan', [ + 'fullcalendarEventSources' => $events, + 'recipes' => $recipes + ]); + } } diff --git a/grocy.openapi.json b/grocy.openapi.json index 7610cad7..196062a0 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -2153,7 +2153,8 @@ "product_groups", "equipment", "api_keys", - "userfields" + "userfields", + "meal_plan" ] }, "ExposedEntitiesPreventListing": { diff --git a/localization/strings.pot b/localization/strings.pot index d478ac0e..5456584e 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -1230,3 +1230,9 @@ msgstr "" msgid "Consume %1$s of %2$s" msgstr "" + +msgid "Meal plan" +msgstr "" + +msgid "Add recipe to %s" +msgstr "" diff --git a/migrations/0070.sql b/migrations/0070.sql new file mode 100644 index 00000000..c6c1c910 --- /dev/null +++ b/migrations/0070.sql @@ -0,0 +1,8 @@ +CREATE TABLE meal_plan ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + day DATE NOT NULL, + recipe_id INTEGER NOT NULL, + row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')), + + UNIQUE(day, recipe_id) +); diff --git a/public/css/grocy.css b/public/css/grocy.css index 8f806b6c..1ec0fced 100644 --- a/public/css/grocy.css +++ b/public/css/grocy.css @@ -207,8 +207,15 @@ input::-webkit-inner-spin-button { padding-right: 0.75rem !important; } +.btn-group-xs > .btn, .btn-xs { + padding : 0.25rem 0.4rem; + font-size : 0.875rem; + line-height : 0.5; + border-radius: 0.2rem; +} + /* Third party component customizations - DataTables */ -td { +.dataTable td { vertical-align: middle !important; } diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js new file mode 100644 index 00000000..aebfccd0 --- /dev/null +++ b/public/viewjs/mealplan.js @@ -0,0 +1,100 @@ +var calendar = $("#calendar").fullCalendar({ + "themeSystem": "bootstrap4", + "header": { + "left": "title", + "center": "", + "right": "prev,today,next" + }, + "weekNumbers": false, + "eventLimit": true, + "eventSources": fullcalendarEventSources, + "defaultView": "basicWeek", + "viewRender": function(view) + { + $(".fc-day-header").append(''); + }, + "eventRender": function(event, element) + { + var recipe = JSON.parse(event.recipe); + + element.removeClass("fc-event"); + element.addClass("text-center"); + element.attr("data-recipe", event.recipe); + element.attr("data-meal-plan-entry", event.mealPlanEntry); + element.html('
' + recipe.name + '
'); + if (recipe.picture_file_name && !recipe.picture_file_name.isEmpty()) + { + element.html(element.html() + '') + } + } +}); + +$(document).on("click", ".add-recipe-button", function(e) +{ + var day = $(this).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"); +}); + +$("#add-recipe-modal").on("shown.bs.modal", function(e) +{ + Grocy.Components.RecipePicker.GetInputElement().focus(); +}) + +$(document).on("click", ".remove-recipe-button", function(e) +{ + var mealPlanEntry = JSON.parse($(this).parent().parent().attr("data-meal-plan-entry")); + + Grocy.Api.Delete('objects/meal_plan/' + mealPlanEntry.id.toString(), { }, + function(result) + { + calendar.fullCalendar('removeEvents', [mealPlanEntry.id]); + }, + 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) + } + ); +}); + +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(); + } + } +}); diff --git a/routes.php b/routes.php index fd880561..aeb1131a 100644 --- a/routes.php +++ b/routes.php @@ -57,6 +57,7 @@ $app->group('', function() $this->get('/recipes', '\Grocy\Controllers\RecipesController:Overview'); $this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm'); $this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm'); + $this->get('/mealplan', '\Grocy\Controllers\RecipesController:MealPlan'); } // Chore routes diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index 9e76b15a..282bc061 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -22,6 +22,14 @@ class DemoDataGeneratorService extends BaseService $loremIpsum = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'; $loremIpsumWithHtmlFormattings = "

Lorem ipsum

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

Lorem ipsum

Lorem ipsum dolor sit amet, consetetur \r\nsadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et \r\ndolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et\r\n justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea \r\ntakimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit \r\namet, consetetur sadipscing elitr,\r\n sed diam nonumy eirmod tempor invidunt ut labore et dolore magna \r\naliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo \r\ndolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus \r\nest Lorem ipsum dolor sit amet.

"; + $mondayThisWeek = date('Y-m-d', strtotime('monday this week')); + $tuesdayThisWeek = date('Y-m-d', strtotime('tuesday this week')); + $wednesdayThisWeek = date('Y-m-d', strtotime('wednesday this week')); + $thursdayThisWeek = date('Y-m-d', strtotime('thursday this week')); + $fridayThisWeek = date('Y-m-d', strtotime('friday this week')); + $saturdayThisWeek = date('Y-m-d', strtotime('saturday this week')); + $sundayThisWeek = date('Y-m-d', strtotime('sunday this week')); + $sql = " UPDATE users SET username = '{$this->__t_sql('Demo User')}' WHERE id = 1; INSERT INTO users (username, password) VALUES ('{$this->__t_sql('Demo User')} 2', 'x'); @@ -103,6 +111,14 @@ class DemoDataGeneratorService extends BaseService INSERt INTO recipes_nestings(recipe_id, includes_recipe_id) VALUES (6, 4); INSERt INTO recipes_nestings(recipe_id, includes_recipe_id) VALUES (6, 5); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$mondayThisWeek}', 1); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$tuesdayThisWeek}', 2); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$wednesdayThisWeek}', 3); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$thursdayThisWeek}', 4); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$fridayThisWeek}', 1); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$saturdayThisWeek}', 2); + INSERt INTO meal_plan(day, recipe_id) VALUES ('{$sundayThisWeek}', 4); + INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Changed towels in the bathroom')}', 'manually', 5); --1 INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2 INSERT INTO chores (name, period_type, period_days) VALUES ('{$this->__t_sql('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3 diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 7b10dbd1..95a6f2d1 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -175,6 +175,12 @@ {{ $__t('Calendar') }} + @endif