diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index 521d2dd0..e210da36 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -63,6 +63,7 @@ class RecipesController extends BaseController 'recipes' => $recipes, 'recipesResolved' => $recipesResolved, 'recipePositionsResolved' => $this->Database->recipes_pos_resolved(), + 'recipeCatagories' => $this->Database->recipe_catagories()->orderBy('name'), 'selectedRecipe' => $selectedRecipe, 'selectedRecipePositionsResolved' => $selectedRecipePositionsResolved, 'products' => $this->Database->products(), @@ -94,6 +95,7 @@ class RecipesController extends BaseController return $this->AppContainer->view->render($response, 'recipeform', [ 'recipe' => $this->Database->recipes($recipeId), 'recipePositions' => $this->Database->recipes_pos()->where('recipe_id', $recipeId), + 'recipeCatagories' => $this->Database->recipe_catagories()->orderBy('name'), 'mode' => 'edit', 'products' => $this->Database->products()->orderBy('name'), 'quantityunits' => $this->Database->quantity_units(), @@ -132,6 +134,30 @@ class RecipesController extends BaseController } } + public function RecipeCatagoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + return $this->AppContainer->view->render($response, 'recipecatagories', [ + 'recipeCatagories' => $this->Database->recipe_catagories()->orderBy('name') + ]); + } + + public function RecipeCatagoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + if ($args['recipeCatagoryId'] == 'new') + { + return $this->AppContainer->view->render($response, 'recipecatagoryform', [ + 'mode' => 'create' + ]); + } + else + { + return $this->AppContainer->view->render($response, 'recipecatagoryform', [ + 'recipeCatagory' => $this->Database->recipe_catagories($args['recipeCatagoryId']), + 'mode' => 'edit' + ]); + } + } + public function MealPlan(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { $recipes = $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(); diff --git a/grocy.openapi.json b/grocy.openapi.json index 194b4765..a883ff3d 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -3304,6 +3304,7 @@ "shopping_list", "shopping_lists", "recipes", + "recipe_catagories", "recipes_pos", "recipes_nestings", "tasks", @@ -3329,6 +3330,7 @@ "shopping_list", "shopping_lists", "recipes", + "recipe_catagories", "recipes_pos", "recipes_nestings", "tasks", diff --git a/migrations/0098.sql b/migrations/0098.sql new file mode 100644 index 00000000..233e1d03 --- /dev/null +++ b/migrations/0098.sql @@ -0,0 +1,9 @@ +ALTER TABLE recipes +ADD recipe_catagory_id INTEGER; + +CREATE TABLE recipe_catagories ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + name TEXT NOT NULL UNIQUE, + description TEXT, + row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) +) diff --git a/public/viewjs/components/recipecatagorypicker.js b/public/viewjs/components/recipecatagorypicker.js new file mode 100644 index 00000000..a911057e --- /dev/null +++ b/public/viewjs/components/recipecatagorypicker.js @@ -0,0 +1,68 @@ +Grocy.Components.RecipeCatagoryPicker = { }; + +Grocy.Components.RecipeCatagoryPicker.GetPicker = function() +{ + return $('#recipe_catagory_id'); +} + +Grocy.Components.RecipeCatagoryPicker.GetInputElement = function() +{ + return $('#recipe_catagory_id_text_input'); +} + +Grocy.Components.RecipeCatagoryPicker.GetValue = function() +{ + return $('#recipe_catagory_id').val(); +} + +Grocy.Components.RecipeCatagoryPicker.SetValue = function(value) +{ + Grocy.Components.RecipeCatagoryPicker.GetInputElement().val(value); + Grocy.Components.RecipeCatagoryPicker.GetInputElement().trigger('change'); +} + +Grocy.Components.RecipeCatagoryPicker.SetId = function(value) +{ + Grocy.Components.RecipeCatagoryPicker.GetPicker().val(value); + Grocy.Components.RecipeCatagoryPicker.GetPicker().data('combobox').refresh(); + Grocy.Components.RecipeCatagoryPicker.GetInputElement().trigger('change'); +} + +Grocy.Components.RecipeCatagoryPicker.Clear = function() +{ + Grocy.Components.RecipeCatagoryPicker.SetValue(''); + Grocy.Components.RecipeCatagoryPicker.SetId(null); +} + +$('.recipe-catagory-combobox').combobox({ + appendId: '_text_input', + bsVersion: '4', + clearIfNoMatch: true +}); + +var prefillByName = Grocy.Components.RecipeCatagoryPicker.GetPicker().parent().data('prefill-by-name').toString(); +if (typeof prefillByName !== "undefined") +{ + possibleOptionElement = $("#recipe_catagory_id option:contains(\"" + prefillByName + "\")").first(); + + if (possibleOptionElement.length > 0) + { + $('#recipe_catagory_id').val(possibleOptionElement.val()); + $('#recipe_catagory_id').data('combobox').refresh(); + $('#recipe_catagory_id').trigger('change'); + + var nextInputElement = $(Grocy.Components.RecipeCatagoryPicker.GetPicker().parent().data('next-input-selector').toString()); + nextInputElement.focus(); + } +} + +var prefillById = Grocy.Components.RecipeCatagoryPicker.GetPicker().parent().data('prefill-by-id').toString(); +if (typeof prefillById !== "undefined") +{ + $('#recipe_catagory_id').val(prefillById); + $('#recipe_catagory_id').data('combobox').refresh(); + $('#recipe_catagory_id').trigger('change'); + + var nextInputElement = $(Grocy.Components.RecipeCatagoryPicker.GetPicker().parent().data('next-input-selector').toString()); + nextInputElement.focus(); +} diff --git a/public/viewjs/recipecatagories.js b/public/viewjs/recipecatagories.js new file mode 100644 index 00000000..9344e747 --- /dev/null +++ b/public/viewjs/recipecatagories.js @@ -0,0 +1,57 @@ +var recipecatagoriesTable = $('#recipecatagories-table').DataTable({ + 'order': [[1, 'asc']], + 'columnDefs': [ + { 'orderable': false, 'targets': 0 }, + { 'searchable': false, "targets": 0 } + ] +}); +$('#recipecatagories-table tbody').removeClass("d-none"); +recipecatagoriesTable.columns.adjust().draw(); + +$("#search").on("keyup", Delay(function() +{ + var value = $(this).val(); + if (value === "all") + { + value = ""; + } + + recipecatagoriesTable.search(value).draw(); +}, 200)); + +$(document).on('click', '.recipe-catagory-delete-button', function (e) +{ + var objectName = $(e.currentTarget).attr('data-recipecatagory-name'); + var objectId = $(e.currentTarget).attr('data-recipecatagory-id'); + + bootbox.confirm({ + message: __t('Are you sure to delete recipe catagory "%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/recipe_catagories/' + objectId, {}, + function(result) + { + window.recipecatagory.href = U('/recipecatagories'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } + } + }); +}); diff --git a/public/viewjs/recipecatagoryform.js b/public/viewjs/recipecatagoryform.js new file mode 100644 index 00000000..b79e917c --- /dev/null +++ b/public/viewjs/recipecatagoryform.js @@ -0,0 +1,63 @@ +$('#save-recipe-catagory-button').on('click', function(e) +{ + e.preventDefault(); + + var jsonData = $('#recipe-catagory-form').serializeJSON(); + Grocy.FrontendHelpers.BeginUiBusy("recipe-catagory-form"); + + if (Grocy.EditMode === 'create') + { + Grocy.Api.Post('objects/recipe_catagories', jsonData, + function(result) + { + Grocy.EditObjectId = result.created_object_id; + window.location.href = U('/recipecatagories'); + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("recipe-catagory-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + Grocy.Api.Put('objects/recipe_catagories/' + Grocy.EditObjectId, jsonData, + function(result) + { + window.location.href = U('/recipecatagories'); + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("recipe-catagory-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } +}); + +$('#recipe-catagory-form input').keyup(function (event) +{ + Grocy.FrontendHelpers.ValidateForm('recipe-catagory-form'); +}); + +$('#recipe-catagory-form input').keydown(function (event) +{ + if (event.keyCode === 13) //Enter + { + event.preventDefault(); + + if (document.getElementById('recipe-catagory-form').checkValidity() === false) //There is at least one validation error + { + return false; + } + else + { + $('#save-recipe-catagory-button').click(); + } + } +}); + +Grocy.Components.UserfieldsForm.Load(); +$('#name').focus(); +Grocy.FrontendHelpers.ValidateForm('recipe-catagory-form'); diff --git a/routes.php b/routes.php index df35f7e5..541f7cf0 100644 --- a/routes.php +++ b/routes.php @@ -70,6 +70,8 @@ $app->group('', function() $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'); + $this->get('/recipecatagories', '\Grocy\Controllers\RecipesController:RecipeCatagoriesList'); + $this->get('/recipecatagory/{recipeCatagoryId}', '\Grocy\Controllers\RecipesController:RecipeCatagoryEditForm'); } // Chore routes diff --git a/views/components/recipecatagorypicker.blade.php b/views/components/recipecatagorypicker.blade.php new file mode 100644 index 00000000..6c6d4523 --- /dev/null +++ b/views/components/recipecatagorypicker.blade.php @@ -0,0 +1,21 @@ +@push('componentScripts') + +@endpush + +@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp +@php if(empty($prefillById)) { $prefillById = ''; } @endphp +@php if(!isset($isRequired)) { $isRequired = true; } @endphp +@php if(empty($hint)) { $hint = ''; } @endphp +@php if(empty($hintId)) { $hintId = ''; } @endphp +@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp + +
+ + +
{{ $__t('You have to select a recipe catagory') }}
+
diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 985902f8..5b28b3f9 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -235,6 +235,14 @@ {{ $__t('Products') }} + @if(GROCY_FEATURE_FLAG_RECIPES) +
  • + + + {{ $__t('Recipe catagories') }} + +
  • + @endif @if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
  • diff --git a/views/recipecatagories.blade.php b/views/recipecatagories.blade.php new file mode 100644 index 00000000..b0027dbe --- /dev/null +++ b/views/recipecatagories.blade.php @@ -0,0 +1,73 @@ +@extends('layout.default') + +@section('title', $__t('Recipe Catagories')) +@section('activeNav', 'recipecatagories') +@section('viewJsName', 'recipecatagories') + +@section('content') +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + + + + + + + @include('components.userfields_thead', array( + 'userfields' => $userfields + )) + + + + + @foreach($recipecatagories as $recipecatagory) + + + + + + @include('components.userfields_tbody', array( + 'userfields' => $userfields, + 'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $recipecatagory->id) + )) + + + @endforeach + +
    {{ $__t('Name') }}{{ $__t('Description') }}
    + + + + + + + + {{ $recipecatagory->name }} + + {{ $recipecatagory->description }} +
    +
    +
    +@stop diff --git a/views/recipecatagoryform.blade.php b/views/recipecatagoryform.blade.php new file mode 100644 index 00000000..8a3ed030 --- /dev/null +++ b/views/recipecatagoryform.blade.php @@ -0,0 +1,41 @@ +@extends('layout.default') + +@if($mode == 'edit') + @section('title', $__t('Edit recipe catagory')) +@else + @section('title', $__t('Create recipe catagory')) +@endif + +@section('viewJsName', 'recipecatagoryform') + +@section('content') +
    +
    +

    @yield('title')

    + + + + @if($mode == 'edit') + + @endif + +
    + +
    + + +
    {{ $__t('A name is required') }}
    +
    + +
    + + +
    + + + + +
    +
    +
    +@stop diff --git a/views/recipeform.blade.php b/views/recipeform.blade.php index 577c7d07..c95afd08 100644 --- a/views/recipeform.blade.php +++ b/views/recipeform.blade.php @@ -61,7 +61,11 @@ 'value' => $value, 'invalidFeedback' => $__t('This cannot be lower than %s', '1'), 'hint' => $__t('The ingredients listed here result in this amount of servings') - )) + )) + + @include('components.recipecatagorypicker', array( + 'recipecatagories' => $recipecatagory + ))
    diff --git a/views/recipes.blade.php b/views/recipes.blade.php index 95871ab2..e828ad34 100644 --- a/views/recipes.blade.php +++ b/views/recipes.blade.php @@ -35,6 +35,12 @@
    + +
    + @include('components.recipecatagorypicker', array( + 'recipeCatagories' => $recipeCatagory + )) +