mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Implemented meal plan sections (closes #370)
This commit is contained in:
parent
1bacd8e13d
commit
2d2700cacb
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
> ⚠️ PHP 8 is now supported and from now on the only tested runtime version (although currently PHP 7.2 should still work).
|
> ⚠️ PHP 8 is now supported and from now on the only tested runtime version (although currently PHP 7.2 should still work).
|
||||||
|
|
||||||
### New feature: (Own) Product/stock entry/chores/batteries labels/barcodes ("grocycode")
|
### New feature: grocycode
|
||||||
|
#### (Own) Product/stock entry/chores/batteries labels/barcodes
|
||||||
- Print own labels/barcodes for products/stock entries/chores/batteries and then scan that code on every place a product/stock entry/chore/battery can be selected
|
- Print own labels/barcodes for products/stock entries/chores/batteries and then scan that code on every place a product/stock entry/chore/battery can be selected
|
||||||
- Can be printed (or downloaded) via
|
- Can be printed (or downloaded) via
|
||||||
- The product/chore/battery edit page
|
- The product/chore/battery edit page
|
||||||
@ -19,6 +20,11 @@
|
|||||||
- https://github.com/grocy/grocy/blob/master/docs/label-printing.md
|
- https://github.com/grocy/grocy/blob/master/docs/label-printing.md
|
||||||
- (Thanks a lot @mistressofjellyfish for the initial work on this)
|
- (Thanks a lot @mistressofjellyfish for the initial work on this)
|
||||||
|
|
||||||
|
### New feature: Meal plan sections
|
||||||
|
- Split the meal plan into sections like Breakfast/Lunch/Dinner
|
||||||
|
- => New button "Configure sections" on the meal plan page to configure the sections (top right corner)
|
||||||
|
- => Each meal plan entry can be assigned to a section
|
||||||
|
|
||||||
### New feature: Shopping list thermal printer support
|
### New feature: Shopping list thermal printer support
|
||||||
- The shopping list can now be printed on a thermal printer
|
- The shopping list can now be printed on a thermal printer
|
||||||
- The printer must be compatible to the `ESC/POS` protocol and needs to be locally attached or network reachable to/by the machine hosting grocy (so the server)
|
- The printer must be compatible to the `ESC/POS` protocol and needs to be locally attached or network reachable to/by the machine hosting grocy (so the server)
|
||||||
|
@ -8,16 +8,15 @@ class RecipesController extends BaseController
|
|||||||
{
|
{
|
||||||
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
// Load +- 8 days to always include week boundaries
|
// Given date is always the first day of the week => load the coming week / 7 days
|
||||||
|
|
||||||
if (isset($request->getQueryParams()['week']) && IsIsoDate($request->getQueryParams()['week']))
|
if (isset($request->getQueryParams()['week']) && IsIsoDate($request->getQueryParams()['week']))
|
||||||
{
|
{
|
||||||
$week = $request->getQueryParams()['week'];
|
$week = $request->getQueryParams()['week'];
|
||||||
$mealPlanWhereTimespan = "day BETWEEN DATE('$week', '-8 day') AND DATE('$week', '+8 day')";
|
$mealPlanWhereTimespan = "day BETWEEN DATE('$week') AND DATE('$week', '+7 days')";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$mealPlanWhereTimespan = "day BETWEEN DATE(DATE('now', 'localtime'), '-8 day') AND DATE(DATE('now', 'localtime'), '+8 day')";
|
$mealPlanWhereTimespan = "day BETWEEN DATE('now', 'localtime', 'weekday 0', '-7 days') AND DATE(DATE('now', 'localtime', 'weekday 0', '-7 days'), '+7 days')";
|
||||||
}
|
}
|
||||||
|
|
||||||
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
|
||||||
@ -58,7 +57,9 @@ class RecipesController extends BaseController
|
|||||||
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved2("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
|
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved2("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
|
||||||
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
||||||
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
|
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
|
||||||
|
'mealplanSections' => $this->getDatabase()->meal_plan_sections()->orderBy('sort_number'),
|
||||||
|
'usedMealplanSections' => $this->getDatabase()->meal_plan_sections()->where("id IN (SELECT section_id FROM meal_plan WHERE $mealPlanWhereTimespan)")->orderBy('sort_number')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +191,30 @@ class RecipesController extends BaseController
|
|||||||
return $this->renderPage($response, 'recipessettings');
|
return $this->renderPage($response, 'recipessettings');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function MealPlanSectionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['sectionId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->renderPage($response, 'mealplansectionform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->renderPage($response, 'mealplansectionform', [
|
||||||
|
'mealplanSection' => $this->getDatabase()->meal_plan_sections($args['sectionId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function MealPlanSectionsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->renderPage($response, 'mealplansections', [
|
||||||
|
'mealplanSections' => $this->getDatabase()->meal_plan_sections()->where('id > 0')->orderBy('sort_number')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(\DI\Container $container)
|
public function __construct(\DI\Container $container)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
|
@ -5579,7 +5579,8 @@
|
|||||||
"stock_log",
|
"stock_log",
|
||||||
"stock",
|
"stock",
|
||||||
"stock_current_locations",
|
"stock_current_locations",
|
||||||
"chores_log"
|
"chores_log",
|
||||||
|
"meal_plan_sections"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ExposedEntityNoListing": {
|
"ExposedEntityNoListing": {
|
||||||
|
@ -377,3 +377,12 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Finnish"
|
msgid "Finnish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Breakfast"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Lunch"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Dinner"
|
||||||
|
msgstr ""
|
||||||
|
@ -2223,3 +2223,24 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Stock entry"
|
msgid "Stock entry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Configure sections"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Meal plan sections"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create meal plan section"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Sections will be ordered by that number on the meal plan"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit meal plan section"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Are you sure to delete meal plan section \"%s\"?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Section"
|
||||||
|
msgstr ""
|
||||||
|
@ -69,7 +69,7 @@ BEGIN
|
|||||||
GROUP BY product_id, product_qu_id;
|
GROUP BY product_id, product_qu_id;
|
||||||
|
|
||||||
-- Create a shadow recipe per meal plan recipe
|
-- Create a shadow recipe per meal plan recipe
|
||||||
INSERT OR REPLACE INTO recipes
|
INSERT INTO recipes
|
||||||
(id, name, type)
|
(id, name, type)
|
||||||
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
||||||
FROM meal_plan
|
FROM meal_plan
|
||||||
@ -77,9 +77,6 @@ BEGIN
|
|||||||
AND type = 'recipe'
|
AND type = 'recipe'
|
||||||
AND recipe_id IS NOT NULL;
|
AND recipe_id IS NOT NULL;
|
||||||
|
|
||||||
DELETE FROM recipes_nestings
|
|
||||||
WHERE recipe_id IN (SELECT id FROM recipes WHERE name IN (SELECT CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT) FROM meal_plan WHERE day = NEW.day) AND type = 'mealplan-shadow');
|
|
||||||
|
|
||||||
INSERT INTO recipes_nestings
|
INSERT INTO recipes_nestings
|
||||||
(recipe_id, includes_recipe_id, servings)
|
(recipe_id, includes_recipe_id, servings)
|
||||||
SELECT (SELECT id FROM recipes WHERE name = CAST(NEW.day AS TEXT) || '#' || CAST(meal_plan.id AS TEXT) AND type = 'mealplan-shadow'), recipe_id, recipe_servings
|
SELECT (SELECT id FROM recipes WHERE name = CAST(NEW.day AS TEXT) || '#' || CAST(meal_plan.id AS TEXT) AND type = 'mealplan-shadow'), recipe_id, recipe_servings
|
||||||
@ -190,7 +187,7 @@ BEGIN
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
-- Create a shadow recipe per meal plan recipe
|
-- Create a shadow recipe per meal plan recipe
|
||||||
INSERT OR REPLACE INTO recipes
|
INSERT INTO recipes
|
||||||
(id, name, type)
|
(id, name, type)
|
||||||
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
||||||
FROM meal_plan
|
FROM meal_plan
|
||||||
@ -292,7 +289,14 @@ BEGIN
|
|||||||
GROUP BY product_id, product_qu_id;
|
GROUP BY product_id, product_qu_id;
|
||||||
|
|
||||||
-- Create a shadow recipe per meal plan recipe
|
-- Create a shadow recipe per meal plan recipe
|
||||||
INSERT OR REPLACE INTO recipes
|
DELETE FROM recipes_nestings
|
||||||
|
WHERE recipe_id IN (SELECT id FROM recipes WHERE name IN (SELECT CAST(NEW.day AS TEXT) || '#' || CAST(NEW.id AS TEXT) FROM meal_plan WHERE day = NEW.day) AND type = 'mealplan-shadow');
|
||||||
|
|
||||||
|
DELETE FROM recipes
|
||||||
|
WHERE type = 'mealplan-shadow'
|
||||||
|
AND name = CAST(NEW.day AS TEXT) || '#' || CAST(NEW.id AS TEXT);
|
||||||
|
|
||||||
|
INSERT INTO recipes
|
||||||
(id, name, type)
|
(id, name, type)
|
||||||
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
SELECT (SELECT MIN(id) - 1 FROM recipes), CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT), 'mealplan-shadow'
|
||||||
FROM meal_plan
|
FROM meal_plan
|
||||||
@ -300,9 +304,6 @@ BEGIN
|
|||||||
AND type = 'recipe'
|
AND type = 'recipe'
|
||||||
AND recipe_id IS NOT NULL;
|
AND recipe_id IS NOT NULL;
|
||||||
|
|
||||||
DELETE FROM recipes_nestings
|
|
||||||
WHERE recipe_id IN (SELECT id FROM recipes WHERE name IN (SELECT CAST(NEW.day AS TEXT) || '#' || CAST(id AS TEXT) FROM meal_plan WHERE day = NEW.day) AND type = 'mealplan-shadow');
|
|
||||||
|
|
||||||
INSERT INTO recipes_nestings
|
INSERT INTO recipes_nestings
|
||||||
(recipe_id, includes_recipe_id, servings)
|
(recipe_id, includes_recipe_id, servings)
|
||||||
SELECT (SELECT id FROM recipes WHERE name = CAST(NEW.day AS TEXT) || '#' || CAST(meal_plan.id AS TEXT) AND type = 'mealplan-shadow'), recipe_id, recipe_servings
|
SELECT (SELECT id FROM recipes WHERE name = CAST(NEW.day AS TEXT) || '#' || CAST(meal_plan.id AS TEXT) AND type = 'mealplan-shadow'), recipe_id, recipe_servings
|
||||||
|
14
migrations/0149.sql
Normal file
14
migrations/0149.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE meal_plan_sections (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
sort_number INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO meal_plan_sections
|
||||||
|
(id, name, sort_number)
|
||||||
|
VALUES
|
||||||
|
(-1, '', -1);
|
||||||
|
|
||||||
|
ALTER TABLE meal_plan
|
||||||
|
ADD section_id INTEGER NOT NULL DEFAULT -1;
|
@ -1116,3 +1116,5 @@ $(document).on("click", ".change-table-columns-rowgroup-toggle", function()
|
|||||||
|
|
||||||
dataTable.draw();
|
dataTable.draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#meal-plan-nav-link").attr("href", $("#meal-plan-nav-link").attr("href") + "?week=" + moment().startOf("week").format("YYYY-MM-DD"));
|
||||||
|
@ -12,23 +12,50 @@ if (!Grocy.MealPlanFirstDayOfWeek.isEmpty())
|
|||||||
firstDay = parseInt(Grocy.MealPlanFirstDayOfWeek);
|
firstDay = parseInt(Grocy.MealPlanFirstDayOfWeek);
|
||||||
}
|
}
|
||||||
|
|
||||||
var calendar = $("#calendar").fullCalendar({
|
$(".calendar").each(function()
|
||||||
"themeSystem": "bootstrap4",
|
{
|
||||||
"header": {
|
var container = $(this);
|
||||||
|
var sectionId = container.attr("data-section-id");
|
||||||
|
var sectionName = container.attr("data-section-name");
|
||||||
|
var isPrimarySection = BoolVal(container.attr("data-primary-section"));
|
||||||
|
var isLastSection = BoolVal(container.attr("data-last-section"));
|
||||||
|
|
||||||
|
var headerConfig = {
|
||||||
"left": "title",
|
"left": "title",
|
||||||
"center": "",
|
"center": "",
|
||||||
"right": "prev,today,next"
|
"right": "prev,today,next"
|
||||||
},
|
};
|
||||||
"weekNumbers": false,
|
if (!isPrimarySection)
|
||||||
"eventLimit": false,
|
|
||||||
"eventSources": fullcalendarEventSources,
|
|
||||||
"defaultView": ($(window).width() < 768) ? "basicDay" : "basicWeek",
|
|
||||||
"firstDay": firstDay,
|
|
||||||
"height": "auto",
|
|
||||||
"defaultDate": GetUriParam("week"),
|
|
||||||
"viewRender": function(view)
|
|
||||||
{
|
{
|
||||||
$(".fc-day-header").prepend('\
|
headerConfig = {
|
||||||
|
"left": "",
|
||||||
|
"center": "",
|
||||||
|
"right": ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
container.fullCalendar({
|
||||||
|
"themeSystem": "bootstrap4",
|
||||||
|
"header": headerConfig,
|
||||||
|
"weekNumbers": false,
|
||||||
|
"eventLimit": false,
|
||||||
|
"eventSources": fullcalendarEventSources,
|
||||||
|
"defaultView": ($(window).width() < 768) ? "agendaDay" : "agendaWeek",
|
||||||
|
"allDayText": sectionName,
|
||||||
|
"minTime": "00:00:00",
|
||||||
|
"maxTime": "00:00:01",
|
||||||
|
"scrollTime": "00:00:00",
|
||||||
|
"firstDay": firstDay,
|
||||||
|
"height": "auto",
|
||||||
|
"defaultDate": GetUriParam("week"),
|
||||||
|
"viewRender": function(view)
|
||||||
|
{
|
||||||
|
if (!isPrimarySection)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".calendar[data-primary-section='true'] .fc-day-header").prepend('\
|
||||||
<div class="btn-group mr-2 my-1"> \
|
<div class="btn-group mr-2 my-1"> \
|
||||||
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button" data-toggle="tooltip" title="' + __t('Add recipe') + '"><i class="fas fa-plus"></i></a></button> \
|
<button type="button" class="btn btn-outline-dark btn-xs add-recipe-button" data-toggle="tooltip" title="' + __t('Add recipe') + '"><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> \
|
<button type="button" class="btn btn-outline-dark btn-xs dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"></button> \
|
||||||
@ -39,114 +66,119 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
</div> \
|
</div> \
|
||||||
</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);
|
||||||
|
|
||||||
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") + ': <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 || weekCosts == 0)
|
|
||||||
{
|
|
||||||
weekRecipeConsumeButtonDisabledClasses = "disabled";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var weekCosts = 0;
|
||||||
var weekRecipeOrderMissingButtonHtml = "";
|
var weekRecipeOrderMissingButtonHtml = "";
|
||||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
var weekRecipeConsumeButtonHtml = "";
|
||||||
|
var weekCostsHtml = "";
|
||||||
|
if (weekRecipe !== null)
|
||||||
{
|
{
|
||||||
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>';
|
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 || weekCosts == 0)
|
||||||
|
{
|
||||||
|
weekRecipeConsumeButtonDisabledClasses = "disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
var weekRecipeOrderMissingButtonHtml = "";
|
||||||
|
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
||||||
|
{
|
||||||
|
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 weeks recipes or products") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-utensils"></i></a>'
|
||||||
}
|
}
|
||||||
|
$(".calendar[data-primary-section='true'] .fc-header-toolbar .fc-center").html("<h4>" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
|
||||||
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 weeks recipes or products") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-utensils"></i></a>'
|
},
|
||||||
}
|
"eventRender": function(event, element)
|
||||||
$(".fc-header-toolbar .fc-center").html("<h4>" + weekCostsHtml + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
|
|
||||||
},
|
|
||||||
"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);
|
|
||||||
|
|
||||||
var additionalTitleCssClasses = "";
|
|
||||||
var doneButtonHtml = '<a class="ml-1 btn btn-outline-secondary btn-xs mealplan-entry-done-button" href="#" data-toggle="tooltip" title="' + __t("Mark this item as done") + '" data-mealplan-entry-id="' + mealPlanEntry.id.toString() + '"><i class="fas fa-check"></i></a>';
|
|
||||||
if (BoolVal(mealPlanEntry.done))
|
|
||||||
{
|
{
|
||||||
additionalTitleCssClasses = "text-strike-through text-muted";
|
element.removeClass("fc-event");
|
||||||
doneButtonHtml = '<a class="ml-1 btn btn-outline-secondary btn-xs mealplan-entry-undone-button" href="#" data-toggle="tooltip" title="' + __t("Mark this item as undone") + '" data-mealplan-entry-id="' + mealPlanEntry.id.toString() + '"><i class="fas fa-undo"></i></a>';
|
element.addClass("text-center");
|
||||||
}
|
element.attr("data-meal-plan-entry", event.mealPlanEntry);
|
||||||
|
|
||||||
if (event.type == "recipe")
|
var mealPlanEntry = JSON.parse(event.mealPlanEntry);
|
||||||
{
|
|
||||||
var recipe = JSON.parse(event.recipe);
|
if (sectionId != mealPlanEntry.section_id)
|
||||||
if (recipe === null || recipe === undefined)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var internalShadowRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", mealPlanEntry.day + "#" + mealPlanEntry.id);
|
var additionalTitleCssClasses = "";
|
||||||
var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", internalShadowRecipe.id);
|
var doneButtonHtml = '<a class="ml-1 btn btn-outline-secondary btn-xs mealplan-entry-done-button" href="#" data-toggle="tooltip" title="' + __t("Mark this item as done") + '" data-mealplan-entry-id="' + mealPlanEntry.id.toString() + '"><i class="fas fa-check"></i></a>';
|
||||||
|
if (BoolVal(mealPlanEntry.done))
|
||||||
element.attr("data-recipe", event.recipe);
|
|
||||||
|
|
||||||
var recipeOrderMissingButtonDisabledClasses = "";
|
|
||||||
if (resolvedRecipe.need_fulfilled_with_shopping_list == 1)
|
|
||||||
{
|
{
|
||||||
recipeOrderMissingButtonDisabledClasses = "disabled";
|
additionalTitleCssClasses = "text-strike-through text-muted";
|
||||||
|
doneButtonHtml = '<a class="ml-1 btn btn-outline-secondary btn-xs mealplan-entry-undone-button" href="#" data-toggle="tooltip" title="' + __t("Mark this item as undone") + '" data-mealplan-entry-id="' + mealPlanEntry.id.toString() + '"><i class="fas fa-undo"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipeConsumeButtonDisabledClasses = "";
|
if (event.type == "recipe")
|
||||||
if (resolvedRecipe.need_fulfilled == 0)
|
|
||||||
{
|
{
|
||||||
recipeConsumeButtonDisabledClasses = "disabled";
|
var recipe = JSON.parse(event.recipe);
|
||||||
}
|
if (recipe === null || recipe === undefined)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var fulfillmentInfoHtml = __t('Enough in stock');
|
var internalShadowRecipe = FindObjectInArrayByPropertyValue(internalRecipes, "name", mealPlanEntry.day + "#" + mealPlanEntry.id);
|
||||||
var fulfillmentIconHtml = '<i class="fas fa-check text-success"></i>';
|
var resolvedRecipe = FindObjectInArrayByPropertyValue(recipesResolved, "recipe_id", internalShadowRecipe.id);
|
||||||
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 mb-1"><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 mb-1"><span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '</h5>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
element.attr("data-recipe", event.recipe);
|
||||||
{
|
|
||||||
fulfillmentIconHtml = "";
|
|
||||||
fulfillmentInfoHtml = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var shoppingListButtonHtml = "";
|
var recipeOrderMissingButtonDisabledClasses = "";
|
||||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
if (resolvedRecipe.need_fulfilled_with_shopping_list == 1)
|
||||||
{
|
{
|
||||||
shoppingListButtonHtml = '<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-mealplan-servings="' + mealPlanEntry.recipe_servings + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-cart-plus"></i></a>';
|
recipeOrderMissingButtonDisabledClasses = "disabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
element.html('\
|
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 mb-1"><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 mb-1"><span class="locale-number locale-number-generic">' + resolvedRecipe.calories + '</span> kcal ' + __t('per serving') + '</h5>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
||||||
|
{
|
||||||
|
fulfillmentIconHtml = "";
|
||||||
|
fulfillmentInfoHtml = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var shoppingListButtonHtml = "";
|
||||||
|
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
||||||
|
{
|
||||||
|
shoppingListButtonHtml = '<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-mealplan-servings="' + mealPlanEntry.recipe_servings + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-cart-plus"></i></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
element.html('\
|
||||||
<div> \
|
<div> \
|
||||||
<h5 class="text-truncate mb-1 cursor-link display-recipe-button ' + additionalTitleCssClasses + '" data-toggle="tooltip" title="' + __t("Display recipe") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-mealplan-servings="' + mealPlanEntry.recipe_servings + '" data-recipe-type="' + recipe.type + '">' + recipe.name + '</h5> \
|
<h5 class="text-truncate mb-1 cursor-link display-recipe-button ' + additionalTitleCssClasses + '" data-toggle="tooltip" title="' + __t("Display recipe") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-mealplan-servings="' + mealPlanEntry.recipe_servings + '" data-recipe-type="' + recipe.type + '">' + recipe.name + '</h5> \
|
||||||
<h5 class="small text-truncate mb-1">' + __n(mealPlanEntry.recipe_servings, "%s serving", "%s servings") + '</h5> \
|
<h5 class="small text-truncate mb-1">' + __n(mealPlanEntry.recipe_servings, "%s serving", "%s servings") + '</h5> \
|
||||||
@ -161,84 +193,63 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
</h5> \
|
</h5> \
|
||||||
</div>');
|
</div>');
|
||||||
|
|
||||||
if (recipe.picture_file_name && !recipe.picture_file_name.isEmpty())
|
if (recipe.picture_file_name && !recipe.picture_file_name.isEmpty())
|
||||||
{
|
|
||||||
element.prepend('<div class="mx-auto mb-1"><img data-src="' + U("/api/files/recipepictures/") + btoa(recipe.picture_file_name) + '?force_serve_as=picture&best_fit_width=400" class="img-fluid rounded-circle 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);
|
element.prepend('<div class="mx-auto mb-1"><img data-src="' + U("/api/files/recipepictures/") + btoa(recipe.picture_file_name) + '?force_serve_as=picture&best_fit_width=400" class="img-fluid rounded-circle lazy"></div>')
|
||||||
|
|
||||||
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 == "product")
|
||||||
if (event.type == "product")
|
|
||||||
{
|
|
||||||
var productDetails = JSON.parse(event.productDetails);
|
|
||||||
if (productDetails === null || productDetails === undefined)
|
|
||||||
{
|
{
|
||||||
return false;
|
var productDetails = JSON.parse(event.productDetails);
|
||||||
}
|
if (productDetails === null || productDetails === undefined)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (productDetails.last_price === null)
|
if (productDetails.last_price === null)
|
||||||
{
|
{
|
||||||
productDetails.last_price = 0;
|
productDetails.last_price = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.attr("data-product-details", event.productDetails);
|
element.attr("data-product-details", event.productDetails);
|
||||||
|
|
||||||
var productOrderMissingButtonDisabledClasses = "disabled";
|
var productOrderMissingButtonDisabledClasses = "disabled";
|
||||||
if (parseFloat(productDetails.stock_amount_aggregated) < parseFloat(mealPlanEntry.product_amount))
|
if (parseFloat(productDetails.stock_amount_aggregated) < parseFloat(mealPlanEntry.product_amount))
|
||||||
{
|
{
|
||||||
productOrderMissingButtonDisabledClasses = "";
|
productOrderMissingButtonDisabledClasses = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
var productConsumeButtonDisabledClasses = "disabled";
|
var productConsumeButtonDisabledClasses = "disabled";
|
||||||
if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount))
|
if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount))
|
||||||
{
|
{
|
||||||
productConsumeButtonDisabledClasses = "";
|
productConsumeButtonDisabledClasses = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
fulfillmentInfoHtml = __t('Not enough in stock');
|
fulfillmentInfoHtml = __t('Not enough in stock');
|
||||||
var fulfillmentIconHtml = '<i class="fas fa-times text-danger"></i>';
|
var fulfillmentIconHtml = '<i class="fas fa-times text-danger"></i>';
|
||||||
if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount))
|
if (parseFloat(productDetails.stock_amount_aggregated) >= parseFloat(mealPlanEntry.product_amount))
|
||||||
{
|
{
|
||||||
var fulfillmentInfoHtml = __t('Enough in stock');
|
var fulfillmentInfoHtml = __t('Enough in stock');
|
||||||
var fulfillmentIconHtml = '<i class="fas fa-check text-success"></i>';
|
var fulfillmentIconHtml = '<i class="fas fa-check text-success"></i>';
|
||||||
}
|
}
|
||||||
|
|
||||||
var costsAndCaloriesPerServing = ""
|
var costsAndCaloriesPerServing = ""
|
||||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||||
{
|
{
|
||||||
costsAndCaloriesPerServing = '<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-currency">' + productDetails.last_price * mealPlanEntry.product_amount + '</span> / <span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '</h5>';
|
costsAndCaloriesPerServing = '<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-currency">' + productDetails.last_price * mealPlanEntry.product_amount + '</span> / <span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '</h5>';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
costsAndCaloriesPerServing = '<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '</h5>';
|
costsAndCaloriesPerServing = '<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '</h5>';
|
||||||
}
|
}
|
||||||
|
|
||||||
var shoppingListButtonHtml = "";
|
var shoppingListButtonHtml = "";
|
||||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_SHOPPINGLIST)
|
||||||
{
|
{
|
||||||
shoppingListButtonHtml = '<a class="btn btn-outline-primary btn-xs show-as-dialog-link ' + productOrderMissingButtonDisabledClasses + '" href="' + U("/shoppinglistitem/new?embedded&updateexistingproduct&product=") + mealPlanEntry.product_id + '&amount=' + mealPlanEntry.product_amount + '" data-toggle="tooltip" title="' + __t("Add to shopping list") + '" data-product-id="' + productDetails.product.id.toString() + '" data-product-name="' + productDetails.product.name + '" data-product-amount="' + mealPlanEntry.product_amount + '"><i class="fas fa-cart-plus"></i></a>';
|
shoppingListButtonHtml = '<a class="btn btn-outline-primary btn-xs show-as-dialog-link ' + productOrderMissingButtonDisabledClasses + '" href="' + U("/shoppinglistitem/new?embedded&updateexistingproduct&product=") + mealPlanEntry.product_id + '&amount=' + mealPlanEntry.product_amount + '" data-toggle="tooltip" title="' + __t("Add to shopping list") + '" data-product-id="' + productDetails.product.id.toString() + '" data-product-name="' + productDetails.product.name + '" data-product-amount="' + mealPlanEntry.product_amount + '"><i class="fas fa-cart-plus"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
element.html('\
|
element.html('\
|
||||||
<div> \
|
<div> \
|
||||||
<h5 class="text-truncate mb-1 cursor-link display-product-button ' + additionalTitleCssClasses + '" data-toggle="tooltip" title="' + __t("Display product") + '" data-product-id="' + productDetails.product.id.toString() + '">' + productDetails.product.name + '</h5> \
|
<h5 class="text-truncate mb-1 cursor-link display-product-button ' + additionalTitleCssClasses + '" data-toggle="tooltip" title="' + __t("Display product") + '" data-product-id="' + productDetails.product.id.toString() + '">' + productDetails.product.name + '</h5> \
|
||||||
<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-quantity-amount">' + mealPlanEntry.product_amount + "</span> " + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural) + '</h5> \
|
<h5 class="small text-truncate mb-1"><span class="locale-number locale-number-quantity-amount">' + mealPlanEntry.product_amount + "</span> " + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural) + '</h5> \
|
||||||
@ -253,9 +264,22 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
</h5> \
|
</h5> \
|
||||||
</div>');
|
</div>');
|
||||||
|
|
||||||
if (productDetails.product.picture_file_name && !productDetails.product.picture_file_name.isEmpty())
|
if (productDetails.product.picture_file_name && !productDetails.product.picture_file_name.isEmpty())
|
||||||
|
{
|
||||||
|
element.prepend('<div class="mx-auto mb-1"><img data-src="' + U("/api/files/productpictures/") + btoa(productDetails.product.picture_file_name) + '?force_serve_as=picture&best_fit_width=400" class="img-fluid rounded-circle lazy"></div>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == "note")
|
||||||
{
|
{
|
||||||
element.prepend('<div class="mx-auto mb-1"><img data-src="' + U("/api/files/productpictures/") + btoa(productDetails.product.picture_file_name) + '?force_serve_as=picture&best_fit_width=400" class="img-fluid rounded-circle lazy"></div>')
|
element.html('\
|
||||||
|
<div> \
|
||||||
|
<h5 class="text-wrap text-break mb-1 ' + additionalTitleCssClasses + '">' + mealPlanEntry.note + '</h5> \
|
||||||
|
<h5> \
|
||||||
|
<a class="ml-1 btn btn-outline-danger btn-xs remove-note-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-trash"></i></a> \
|
||||||
|
<a class="btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-edit"></i></a> \
|
||||||
|
' + doneButtonHtml + ' \
|
||||||
|
</h5> \
|
||||||
|
</div>');
|
||||||
}
|
}
|
||||||
|
|
||||||
var dayRecipeName = event.start.format("YYYY-MM-DD");
|
var dayRecipeName = event.start.format("YYYY-MM-DD");
|
||||||
@ -275,47 +299,48 @@ var calendar = $("#calendar").fullCalendar({
|
|||||||
{
|
{
|
||||||
costsAndCaloriesPerDay = '<h5 class="small text-truncate"><span class="locale-number locale-number-generic">' + dayRecipeResolved.calories + '</span> kcal ' + __t('per day') + '</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>');
|
|
||||||
|
$(".calendar[data-primary-section='true'] .fc-day-header[data-date='" + dayRecipeName + "']").append('<h5 id="day-summary-' + dayRecipeName + '" class="small text-truncate border-top pt-1 pb-0">' + costsAndCaloriesPerDay + '</h5>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eventAfterAllRender": function(view)
|
||||||
|
{
|
||||||
|
if (isPrimarySection)
|
||||||
|
{
|
||||||
|
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
||||||
|
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
firstRender = false
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$(".calendar").addClass("d-none");
|
||||||
|
window.location.reload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLastSection)
|
||||||
|
{
|
||||||
|
$(".fc-axis span").replaceWith(function()
|
||||||
|
{
|
||||||
|
return $("<div />", { html: $(this).html() });
|
||||||
|
});
|
||||||
|
|
||||||
|
RefreshLocaleNumberDisplay();
|
||||||
|
LoadImagesLazy();
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
|
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
||||||
|
{
|
||||||
|
$(".recipe-order-missing-button").addClass("d-none");
|
||||||
|
$(".recipe-consume-button").addClass("d-none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (event.type == "note")
|
});
|
||||||
{
|
|
||||||
element.html('\
|
|
||||||
<div> \
|
|
||||||
<h5 class="text-wrap text-break mb-1 ' + additionalTitleCssClasses + '">' + mealPlanEntry.note + '</h5> \
|
|
||||||
<h5> \
|
|
||||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-note-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-trash"></i></a> \
|
|
||||||
<a class="btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-edit"></i></a> \
|
|
||||||
' + doneButtonHtml + ' \
|
|
||||||
</h5> \
|
|
||||||
</div>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"eventAfterAllRender": function(view)
|
|
||||||
{
|
|
||||||
UpdateUriParam("week", view.start.format("YYYY-MM-DD"));
|
|
||||||
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
firstRender = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
window.location.reload();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshLocaleNumberDisplay();
|
|
||||||
LoadImagesLazy();
|
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
|
||||||
|
|
||||||
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK)
|
|
||||||
{
|
|
||||||
$(".recipe-order-missing-button").addClass("d-none");
|
|
||||||
$(".recipe-consume-button").addClass("d-none");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("click", ".add-recipe-button", function(e)
|
$(document).on("click", ".add-recipe-button", function(e)
|
||||||
@ -325,6 +350,7 @@ $(document).on("click", ".add-recipe-button", function(e)
|
|||||||
$("#add-recipe-modal-title").text(__t("Add recipe on %s", day.toString()));
|
$("#add-recipe-modal-title").text(__t("Add recipe on %s", day.toString()));
|
||||||
$("#day").val(day.toString());
|
$("#day").val(day.toString());
|
||||||
Grocy.Components.RecipePicker.Clear();
|
Grocy.Components.RecipePicker.Clear();
|
||||||
|
$("#section_id_note").val(-1);
|
||||||
$("#add-recipe-modal").modal("show");
|
$("#add-recipe-modal").modal("show");
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
||||||
Grocy.IsMealPlanEntryEditAction = false;
|
Grocy.IsMealPlanEntryEditAction = false;
|
||||||
@ -337,6 +363,7 @@ $(document).on("click", ".add-note-button", function(e)
|
|||||||
$("#add-note-modal-title").text(__t("Add note on %s", day.toString()));
|
$("#add-note-modal-title").text(__t("Add note on %s", day.toString()));
|
||||||
$("#day").val(day.toString());
|
$("#day").val(day.toString());
|
||||||
$("#note").val("");
|
$("#note").val("");
|
||||||
|
$("#section_id_note").val(-1);
|
||||||
$("#add-note-modal").modal("show");
|
$("#add-note-modal").modal("show");
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-note-form");
|
Grocy.FrontendHelpers.ValidateForm("add-note-form");
|
||||||
Grocy.IsMealPlanEntryEditAction = false;
|
Grocy.IsMealPlanEntryEditAction = false;
|
||||||
@ -349,6 +376,7 @@ $(document).on("click", ".add-product-button", function(e)
|
|||||||
$("#add-product-modal-title").text(__t("Add product on %s", day.toString()));
|
$("#add-product-modal-title").text(__t("Add product on %s", day.toString()));
|
||||||
$("#day").val(day.toString());
|
$("#day").val(day.toString());
|
||||||
Grocy.Components.ProductPicker.Clear();
|
Grocy.Components.ProductPicker.Clear();
|
||||||
|
$("#section_id_note").val(-1);
|
||||||
$("#add-product-modal").modal("show");
|
$("#add-product-modal").modal("show");
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-product-form");
|
Grocy.FrontendHelpers.ValidateForm("add-product-form");
|
||||||
Grocy.IsMealPlanEntryEditAction = false;
|
Grocy.IsMealPlanEntryEditAction = false;
|
||||||
@ -365,6 +393,7 @@ $(document).on("click", ".edit-meal-plan-entry-button", function(e)
|
|||||||
$("#recipe_servings").val(mealPlanEntry.recipe_servings);
|
$("#recipe_servings").val(mealPlanEntry.recipe_servings);
|
||||||
Grocy.Components.RecipePicker.SetId(mealPlanEntry.recipe_id);
|
Grocy.Components.RecipePicker.SetId(mealPlanEntry.recipe_id);
|
||||||
$("#add-recipe-modal").modal("show");
|
$("#add-recipe-modal").modal("show");
|
||||||
|
$("#section_id_recipe").val(mealPlanEntry.section_id);
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
Grocy.FrontendHelpers.ValidateForm("add-recipe-form");
|
||||||
}
|
}
|
||||||
else if (mealPlanEntry.type == "product")
|
else if (mealPlanEntry.type == "product")
|
||||||
@ -373,6 +402,7 @@ $(document).on("click", ".edit-meal-plan-entry-button", function(e)
|
|||||||
$("#day").val(mealPlanEntry.day.toString());
|
$("#day").val(mealPlanEntry.day.toString());
|
||||||
Grocy.Components.ProductPicker.SetId(mealPlanEntry.product_id);
|
Grocy.Components.ProductPicker.SetId(mealPlanEntry.product_id);
|
||||||
$("#add-product-modal").modal("show");
|
$("#add-product-modal").modal("show");
|
||||||
|
$("#section_id_product").val(mealPlanEntry.section_id);
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-product-form");
|
Grocy.FrontendHelpers.ValidateForm("add-product-form");
|
||||||
Grocy.Components.ProductPicker.GetPicker().trigger("change");
|
Grocy.Components.ProductPicker.GetPicker().trigger("change");
|
||||||
}
|
}
|
||||||
@ -382,6 +412,7 @@ $(document).on("click", ".edit-meal-plan-entry-button", function(e)
|
|||||||
$("#day").val(mealPlanEntry.day.toString());
|
$("#day").val(mealPlanEntry.day.toString());
|
||||||
$("#note").val(mealPlanEntry.note);
|
$("#note").val(mealPlanEntry.note);
|
||||||
$("#add-note-modal").modal("show");
|
$("#add-note-modal").modal("show");
|
||||||
|
$("#section_id_note").val(mealPlanEntry.section_id);
|
||||||
Grocy.FrontendHelpers.ValidateForm("add-note-form");
|
Grocy.FrontendHelpers.ValidateForm("add-note-form");
|
||||||
}
|
}
|
||||||
Grocy.IsMealPlanEntryEditAction = true;
|
Grocy.IsMealPlanEntryEditAction = true;
|
||||||
@ -450,9 +481,13 @@ $('#save-add-recipe-button').on('click', function(e)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var formData = $('#add-recipe-form').serializeJSON();
|
||||||
|
formData.section_id = formData.section_id_recipe;
|
||||||
|
delete formData.section_id_recipe;
|
||||||
|
|
||||||
if (Grocy.IsMealPlanEntryEditAction)
|
if (Grocy.IsMealPlanEntryEditAction)
|
||||||
{
|
{
|
||||||
Grocy.Api.Put('objects/meal_plan/' + Grocy.MealPlanEntryEditObjectId.toString(), $('#add-recipe-form').serializeJSON(),
|
Grocy.Api.Put('objects/meal_plan/' + Grocy.MealPlanEntryEditObjectId.toString(), formData,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
@ -465,7 +500,7 @@ $('#save-add-recipe-button').on('click', function(e)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.Api.Post('objects/meal_plan', $('#add-recipe-form').serializeJSON(),
|
Grocy.Api.Post('objects/meal_plan', formData,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
@ -494,6 +529,8 @@ $('#save-add-note-button').on('click', function(e)
|
|||||||
|
|
||||||
var jsonData = $('#add-note-form').serializeJSON();
|
var jsonData = $('#add-note-form').serializeJSON();
|
||||||
jsonData.day = $("#day").val();
|
jsonData.day = $("#day").val();
|
||||||
|
jsonData.section_id = formData.section_id_note;
|
||||||
|
delete formData.section_id_note;
|
||||||
|
|
||||||
if (Grocy.IsMealPlanEntryEditAction)
|
if (Grocy.IsMealPlanEntryEditAction)
|
||||||
{
|
{
|
||||||
@ -545,6 +582,8 @@ $('#save-add-product-button').on('click', function(e)
|
|||||||
delete jsonData.amount;
|
delete jsonData.amount;
|
||||||
jsonData.product_qu_id = $("#qu_id").val();;
|
jsonData.product_qu_id = $("#qu_id").val();;
|
||||||
delete jsonData.qu_id;
|
delete jsonData.qu_id;
|
||||||
|
jsonData.section_id = jsonData.section_id_product;
|
||||||
|
delete jsonData.section_id_product;
|
||||||
|
|
||||||
if (Grocy.IsMealPlanEntryEditAction)
|
if (Grocy.IsMealPlanEntryEditAction)
|
||||||
{
|
{
|
||||||
@ -939,16 +978,19 @@ $(document).on("click", ".mealplan-entry-undone-button", function(e)
|
|||||||
|
|
||||||
$(window).one("resize", function()
|
$(window).one("resize", function()
|
||||||
{
|
{
|
||||||
// Automatically switch the calendar to "basicDay" view on small screens
|
// Automatically switch the calendar to "agendaDay" view on small screens and to "agendaWeek" otherwise
|
||||||
// and to "basicWeek" otherwise
|
var windowWidth = $(window).width();
|
||||||
if ($(window).width() < 768)
|
$(".calendar").each(function()
|
||||||
{
|
{
|
||||||
calendar.fullCalendar("changeView", "basicDay");
|
if (windowWidth < 768)
|
||||||
}
|
{
|
||||||
else
|
$(this).fullCalendar("changeView", "agendaDay");
|
||||||
{
|
}
|
||||||
calendar.fullCalendar("changeView", "basicWeek");
|
else
|
||||||
}
|
{
|
||||||
|
$(this).fullCalendar("changeView", "agendaWeek");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||||
|
75
public/viewjs/mealplansectionform.js
Normal file
75
public/viewjs/mealplansectionform.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
$('#save-mealplansection-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonData = $('#mealplansection-form').serializeJSON();
|
||||||
|
Grocy.FrontendHelpers.BeginUiBusy("mealplansection-form");
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('objects/meal_plan_sections', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
if (GetUriParam("embedded") !== undefined)
|
||||||
|
{
|
||||||
|
window.parent.postMessage(WindowMessageBag("Reload"), Grocy.BaseUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location.href = U('/mealplansections');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("mealplansection-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Put('objects/meal_plan_sections/' + Grocy.EditObjectId, jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
if (GetUriParam("embedded") !== undefined)
|
||||||
|
{
|
||||||
|
window.parent.postMessage(WindowMessageBag("Reload"), Grocy.BaseUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location.href = U('/mealplansections');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("mealplansection-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#mealplansection-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('mealplansection-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#mealplansection-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('mealplansection-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-mealplansections-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('mealplansection-form');
|
||||||
|
$('#name').focus();
|
63
public/viewjs/mealplansections.js
Normal file
63
public/viewjs/mealplansections.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
var mealplanSectionsTable = $('#mealplansections-table').DataTable({
|
||||||
|
'order': [[2, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 },
|
||||||
|
{ 'searchable': false, "targets": 0 }
|
||||||
|
].concat($.fn.dataTable.defaults.columnDefs)
|
||||||
|
});
|
||||||
|
$('#mealplansections-table tbody').removeClass("d-none");
|
||||||
|
mealplanSectionsTable.columns.adjust().draw();
|
||||||
|
|
||||||
|
$("#search").on("keyup", Delay(function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
mealplanSectionsTable.search(value).draw();
|
||||||
|
}, 200));
|
||||||
|
|
||||||
|
$("#clear-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
$("#search").val("");
|
||||||
|
mealplanSectionsTable.search("").draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.mealplansection-delete-button', function(e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-mealplansection-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-mealplansection-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: __t('Are you sure to delete meal plan section "%s"?', 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.Api.Delete('objects/meal_plan_sections/' + objectId, {},
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/mealplansections');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -87,6 +87,8 @@ $app->group('', function (RouteCollectorProxy $group) {
|
|||||||
$group->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
|
$group->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
|
||||||
$group->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
|
$group->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
|
||||||
$group->get('/mealplan', '\Grocy\Controllers\RecipesController:MealPlan');
|
$group->get('/mealplan', '\Grocy\Controllers\RecipesController:MealPlan');
|
||||||
|
$group->get('/mealplansections', '\Grocy\Controllers\RecipesController:MealPlanSectionsList');
|
||||||
|
$group->get('/mealplansection/{sectionId}', '\Grocy\Controllers\RecipesController:MealPlanSectionEditForm');
|
||||||
$group->get('/recipessettings', '\Grocy\Controllers\RecipesController:RecipesSettings');
|
$group->get('/recipessettings', '\Grocy\Controllers\RecipesController:RecipesSettings');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,15 +140,22 @@ 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, 4);
|
||||||
INSERT INTO recipes_nestings(recipe_id, includes_recipe_id) VALUES (6, 5);
|
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_sections (name, sort_number) VALUES ('{$this->__t_sql('Breakfast')}', 10);
|
||||||
INSERT INTO meal_plan(day, recipe_id) VALUES ('{$tuesdayThisWeek}', 2);
|
INSERT INTO meal_plan_sections (name, sort_number) VALUES ('{$this->__t_sql('Lunch')}', 20);
|
||||||
INSERT INTO meal_plan(day, recipe_id) VALUES ('{$wednesdayThisWeek}', 3);
|
INSERT INTO meal_plan_sections (name, sort_number) VALUES ('{$this->__t_sql('Dinner')}', 30);
|
||||||
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, section_id) VALUES ('{$mondayThisWeek}', 1, 2);
|
||||||
INSERT INTO meal_plan(day, recipe_id) VALUES ('{$saturdayThisWeek}', 2);
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$tuesdayThisWeek}', 2, 2);
|
||||||
INSERT INTO meal_plan(day, recipe_id) VALUES ('{$sundayThisWeek}', 4);
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$wednesdayThisWeek}', 3, 3);
|
||||||
INSERT INTO meal_plan(day, type, note) VALUES ('{$tuesdayThisWeek}', 'note', '{$this->__t_sql('This is a note')}');
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$thursdayThisWeek}', 4, 1);
|
||||||
INSERT INTO meal_plan(day, type, product_id, product_amount) VALUES (DATE('{$mondayThisWeek}', '-1 days'), 'product', 3, 1);
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$fridayThisWeek}', 2, 2);
|
||||||
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$saturdayThisWeek}', 1, 2);
|
||||||
|
INSERT INTO meal_plan(day, recipe_id, section_id) VALUES ('{$sundayThisWeek}', 4, 2);
|
||||||
|
INSERT INTO meal_plan(day, type, note, section_id) VALUES ('{$tuesdayThisWeek}', 'note', '{$this->__t_sql('This is a note')}', 1);
|
||||||
|
INSERT INTO meal_plan(day, type, product_id, product_amount, section_id) VALUES (DATE('{$mondayThisWeek}', '-1 days'), 'product', 3, 1, 3);
|
||||||
|
INSERT INTO meal_plan(day, type, product_id, product_amount, section_id) VALUES (DATE('{$tuesdayThisWeek}', '-1 days'), 'product', 9, 1, 1);
|
||||||
|
INSERT INTO meal_plan(day, type, product_id, product_amount, section_id) VALUES (DATE('{$thursdayThisWeek}', '-1 days'), 'product', 25, 1, 1);
|
||||||
|
INSERT INTO meal_plan(day, type, note, section_id) VALUES ('{$saturdayThisWeek}', 'note', '{$this->__t_sql('Some good snacks')}', 3);
|
||||||
|
|
||||||
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('Changed towels in the bathroom')}', 'manually', 5); --1
|
||||||
INSERT INTO chores (name, period_type, period_days, assignment_type, assignment_config, next_execution_assigned_to_user_id) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7, 'random', '1,2,3,4', 1); --2
|
INSERT INTO chores (name, period_type, period_days, assignment_type, assignment_config, next_execution_assigned_to_user_id) VALUES ('{$this->__t_sql('Cleaned the kitchen floor')}', 'dynamic-regular', 7, 'random', '1,2,3,4', 1); --2
|
||||||
|
@ -188,7 +188,8 @@
|
|||||||
data-placement="right"
|
data-placement="right"
|
||||||
title="{{ $__t('Meal plan') }}"
|
title="{{ $__t('Meal plan') }}"
|
||||||
data-nav-for-page="mealplan">
|
data-nav-for-page="mealplan">
|
||||||
<a class="nav-link discrete-link"
|
<a id="meal-plan-nav-link"
|
||||||
|
class="nav-link discrete-link"
|
||||||
href="{{ $U('/mealplan') }}">
|
href="{{ $U('/mealplan') }}">
|
||||||
<i class="fas fa-paper-plane"></i>
|
<i class="fas fa-paper-plane"></i>
|
||||||
<span class="nav-link-text">{{ $__t('Meal plan') }}</span>
|
<span class="nav-link-text">{{ $__t('Meal plan') }}</span>
|
||||||
|
@ -24,6 +24,39 @@
|
|||||||
max-height: 140px;
|
max-height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-time-grid-container,
|
||||||
|
hr.fc-divider {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-axis {
|
||||||
|
width: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-axis div {
|
||||||
|
transform: translateX(-50%) translateY(-50%) rotate(-90deg);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.8em;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-content-skeleton {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar[data-primary-section='false'] .fc-toolbar.fc-header-toolbar,
|
||||||
|
.calendar[data-primary-section='false'] .fc-head {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar[data-primary-section='false'] {
|
||||||
|
border-top: #d6d6d6 solid 5px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 400px) {
|
@media (min-width: 400px) {
|
||||||
.table-inline-menu.dropdown-menu {
|
.table-inline-menu.dropdown-menu {
|
||||||
width: 200px !important;
|
width: 200px !important;
|
||||||
@ -47,17 +80,56 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="title">@yield('title')</h2>
|
<div class="title-related-links">
|
||||||
|
<h2 class="title">@yield('title')</h2>
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#related-links">
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
||||||
|
id="related-links">
|
||||||
|
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
|
||||||
|
href="{{ $U('/mealplansections') }}">
|
||||||
|
{{ $__t('Configure sections') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-2">
|
<hr class="my-2">
|
||||||
|
|
||||||
|
@foreach($usedMealplanSections as $mealplanSection)
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div id="calendar"></div>
|
<div class="calendar"
|
||||||
|
data-section-id="{{ $mealplanSection->id }}"
|
||||||
|
data-section-name="{{ $mealplanSection->name }}"
|
||||||
|
data-primary-section="{{ BoolToString($loop->first) }}"
|
||||||
|
{{-- $loop->last doesn't work however, is always null... --}}
|
||||||
|
data-last-section="{{ BoolToString(array_values(array_slice($usedMealplanSections->fetchAll(), -1))[0]->id == $mealplanSection->id) }}">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
{{-- Default empty calendar/section when no single meal plan entry is in the given date range --}}
|
||||||
|
@if($usedMealplanSections->count() === 0)
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="calendar"
|
||||||
|
data-section-id="-1"
|
||||||
|
data-section-name=""
|
||||||
|
data-primary-section="true"
|
||||||
|
data-last-section="true">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="modal fade"
|
<div class="modal fade"
|
||||||
id="add-recipe-modal"
|
id="add-recipe-modal"
|
||||||
@ -87,6 +159,18 @@
|
|||||||
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
|
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
|
||||||
))
|
))
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="period_type">{{ $__t('Section') }}</label>
|
||||||
|
<select class="custom-control custom-select"
|
||||||
|
id="section_id_recipe"
|
||||||
|
name="section_id_recipe"
|
||||||
|
required>
|
||||||
|
@foreach($mealplanSections as $mealplanSection)
|
||||||
|
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
id="day"
|
id="day"
|
||||||
name="day"
|
name="day"
|
||||||
@ -130,6 +214,18 @@
|
|||||||
name="note"></textarea>
|
name="note"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="period_type">{{ $__t('Section') }}</label>
|
||||||
|
<select class="custom-control custom-select"
|
||||||
|
id="section_id_note"
|
||||||
|
name="section_id_note"
|
||||||
|
required>
|
||||||
|
@foreach($mealplanSections as $mealplanSection)
|
||||||
|
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="type"
|
name="type"
|
||||||
value="note">
|
value="note">
|
||||||
@ -171,6 +267,18 @@
|
|||||||
'additionalGroupCssClasses' => 'mb-0'
|
'additionalGroupCssClasses' => 'mb-0'
|
||||||
))
|
))
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="period_type">{{ $__t('Section') }}</label>
|
||||||
|
<select class="custom-control custom-select"
|
||||||
|
id="section_id_product"
|
||||||
|
name="section_id_product"
|
||||||
|
required>
|
||||||
|
@foreach($mealplanSections as $mealplanSection)
|
||||||
|
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="type"
|
name="type"
|
||||||
value="product">
|
value="product">
|
||||||
|
62
views/mealplansectionform.blade.php
Normal file
62
views/mealplansectionform.blade.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
@section('title', $__t('Edit meal plan section'))
|
||||||
|
@else
|
||||||
|
@section('title', $__t('Create meal plan section'))
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@section('viewJsName', 'mealplansectionform')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h2 class="title">@yield('title')</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-2">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-12">
|
||||||
|
<script>
|
||||||
|
Grocy.EditMode = '{{ $mode }}';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@if($mode == 'edit')
|
||||||
|
<script>
|
||||||
|
Grocy.EditObjectId = {{ $mealplanSection->id }};
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form id="mealplansection-form"
|
||||||
|
novalidate>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">{{ $__t('Name') }}</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value="@if($mode == 'edit'){{ $mealplanSection->name }}@endif">
|
||||||
|
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@php if($mode == 'edit' && !empty($mealplanSection->sort_number)) { $value = $mealplanSection->sort_number; } else { $value = ''; } @endphp
|
||||||
|
@include('components.numberpicker', array(
|
||||||
|
'id' => 'sort_number',
|
||||||
|
'label' => 'Sort number',
|
||||||
|
'min' => 0,
|
||||||
|
'value' => $value,
|
||||||
|
'isRequired' => false,
|
||||||
|
'hint' => $__t('Sections will be ordered by that number on the meal plan')
|
||||||
|
))
|
||||||
|
|
||||||
|
<button id="save-mealplansection-button"
|
||||||
|
class="btn btn-success">{{ $__t('Save') }}</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
111
views/mealplansections.blade.php
Normal file
111
views/mealplansections.blade.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $__t('Meal plan sections'))
|
||||||
|
@section('activeNav', 'mealplansections')
|
||||||
|
@section('viewJsName', 'mealplansections')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="title-related-links">
|
||||||
|
<h2 class="title">@yield('title')</h2>
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#table-filter-row">
|
||||||
|
<i class="fas fa-filter"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#related-links">
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
||||||
|
id="related-links">
|
||||||
|
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
|
||||||
|
href="{{ $U('/mealplansection/new?embedded') }}">
|
||||||
|
{{ $__t('Add') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-2">
|
||||||
|
|
||||||
|
<div class="row collapse d-md-flex"
|
||||||
|
id="table-filter-row">
|
||||||
|
<div class="col-12 col-md-6 col-xl-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text"
|
||||||
|
id="search"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="{{ $__t('Search') }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="float-right">
|
||||||
|
<a id="clear-filter-button"
|
||||||
|
class="btn btn-sm btn-outline-info"
|
||||||
|
href="#">
|
||||||
|
{{ $__t('Clear filter') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table id="mealplansections-table"
|
||||||
|
class="table table-sm table-striped nowrap w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{{ $__t('Table options') }}"
|
||||||
|
data-table-selector="#mealplansections-table"
|
||||||
|
href="#"><i class="fas fa-eye"></i></a>
|
||||||
|
</th>
|
||||||
|
<th>{{ $__t('Name') }}</th>
|
||||||
|
<th>{{ $__t('Sort number') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="d-none">
|
||||||
|
@foreach($mealplanSections as $mealplanSection)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content border-right">
|
||||||
|
<a class="btn btn-info btn-sm show-as-dialog-link"
|
||||||
|
href="{{ $U('/mealplansection/') }}{{ $mealplanSection->id }}?embedded"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{{ $__t('Edit this item') }}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger btn-sm mealplansection-delete-button"
|
||||||
|
href="#"
|
||||||
|
data-mealplansection-id="{{ $mealplanSection->id }}"
|
||||||
|
data-mealplansection-name="{{ $mealplanSection->name }}"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{{ $__t('Delete this item') }}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $mealplanSection->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $mealplanSection->sort_number }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
Loading…
x
Reference in New Issue
Block a user