mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 20:36:15 +02:00
New Feature Recipe Catagories
This commit is contained in:
parent
49edd011b4
commit
b3aa497fb7
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
9
migrations/0098.sql
Normal file
9
migrations/0098.sql
Normal file
|
|
@ -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'))
|
||||
)
|
||||
68
public/viewjs/components/recipecatagorypicker.js
Normal file
68
public/viewjs/components/recipecatagorypicker.js
Normal file
|
|
@ -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();
|
||||
}
|
||||
57
public/viewjs/recipecatagories.js
Normal file
57
public/viewjs/recipecatagories.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
63
public/viewjs/recipecatagoryform.js
Normal file
63
public/viewjs/recipecatagoryform.js
Normal file
|
|
@ -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');
|
||||
|
|
@ -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
|
||||
|
|
|
|||
21
views/components/recipecatagorypicker.blade.php
Normal file
21
views/components/recipecatagorypicker.blade.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@push('componentScripts')
|
||||
<script src="{{ $U('/viewjs/components/recipecatagorypicker.js', true) }}?v={{ $version }}"></script>
|
||||
@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
|
||||
|
||||
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}" data-prefill-by-name="{{ $prefillByName }}" data-prefill-by-id="{{ $prefillById }}">
|
||||
<label for="recipe_catagory_id">{{ $__t('Recipe Catagory') }} <span id="{{ $hintId }}" class="small text-muted">{{ $hint }}</span></label>
|
||||
<select class="form-control recipe-catagory-combobox" id="recipe_catagory_id" name="recipe_catagory_id" @if($isRequired) required @endif>
|
||||
<option value=""></option>
|
||||
@foreach($recipeCatagories as $recipeCatagory)
|
||||
<option value="{{ $recipeCatagory->id }}">{{ $recipeCatagory->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ $__t('You have to select a recipe catagory') }}</div>
|
||||
</div>
|
||||
|
|
@ -235,6 +235,14 @@
|
|||
<span class="nav-link-text">{{ $__t('Products') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@if(GROCY_FEATURE_FLAG_RECIPES)
|
||||
<li data-nav-for-page="recipecatagories" data-sub-menu-of="#top-nav-manager-master-data">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/recipecatagories') }}">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<span class="nav-link-text">{{ $__t('Recipe catagories') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||
<li data-nav-for-page="locations" data-sub-menu-of="#top-nav-manager-master-data">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/locations') }}">
|
||||
|
|
|
|||
73
views/recipecatagories.blade.php
Normal file
73
views/recipecatagories.blade.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
@extends('layout.default')
|
||||
|
||||
@section('title', $__t('Recipe Catagories'))
|
||||
@section('activeNav', 'recipecatagories')
|
||||
@section('viewJsName', 'recipecatagories')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>
|
||||
@yield('title')
|
||||
<a class="btn btn-outline-dark" href="{{ $U('/recipecatagory/new') }}">
|
||||
<i class="fas fa-plus"></i> {{ $__t('Add') }}
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary" href="{{ $U('/userfields?entity=recipe_catagories') }}">
|
||||
<i class="fas fa-sliders-h"></i> {{ $__t('Configure userfields') }}
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||
<label for="search">{{ $__t('Search') }}</label> <i class="fas fa-search"></i>
|
||||
<input type="text" class="form-control" id="search">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table id="recipecatagories-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-right"></th>
|
||||
<th>{{ $__t('Name') }}</th>
|
||||
<th>{{ $__t('Description') }}</th>
|
||||
|
||||
@include('components.userfields_thead', array(
|
||||
'userfields' => $userfields
|
||||
))
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="d-none">
|
||||
@foreach($recipecatagories as $recipecatagory)
|
||||
<tr>
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/recipecatagory/') }}{{ $recipecatagory->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm recipe-catagory-delete-button" href="#" data-recipecatagory-id="{{ $recipecatagory->id }}" data-recipecatagory-name="{{ $recipecatagory->name }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ $recipecatagory->name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $recipecatagory->description }}
|
||||
</td>
|
||||
|
||||
@include('components.userfields_tbody', array(
|
||||
'userfields' => $userfields,
|
||||
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $recipecatagory->id)
|
||||
))
|
||||
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
41
views/recipecatagoryform.blade.php
Normal file
41
views/recipecatagoryform.blade.php
Normal file
|
|
@ -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')
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
<h1>@yield('title')</h1>
|
||||
|
||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||
|
||||
@if($mode == 'edit')
|
||||
<script>Grocy.EditObjectId = {{ $recipecatagory->id }};</script>
|
||||
@endif
|
||||
|
||||
<form id="recipe-catagory-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'){{ $recipecatagory->name }}@endif">
|
||||
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">{{ $__t('Description') }}</label>
|
||||
<textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $recipecatagory->description }}@endif</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<button id="save-recipe-catagory-button" class="btn btn-success">{{ $__t('Save') }}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
|
@ -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
|
||||
))
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@
|
|||
<option class="bg-danger" value="notenoughinstock">{{ $__t('Not enough in stock') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
@include('components.recipecatagorypicker', array(
|
||||
'recipeCatagories' => $recipeCatagory
|
||||
))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs mt-3">
|
||||
|
|
@ -58,6 +64,7 @@
|
|||
<th class="d-none">Hidden status for sorting of "Requirements fulfilled" column</th>
|
||||
<th class="d-none">Hidden status for filtering by status</th>
|
||||
<th class="d-none">Hidden recipe ingredient product names</th>
|
||||
<th class="d-none">Hidden recipe catagory names</th>
|
||||
|
||||
@include('components.userfields_thead', array(
|
||||
'userfields' => $userfields
|
||||
|
|
@ -89,6 +96,9 @@
|
|||
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePos->product_id)->name . ' ' }}
|
||||
@endforeach
|
||||
</td>
|
||||
<td>
|
||||
{{ FindObjectInArrayByPropertyValue($recipeCatagories, 'id', $recipe->recipy_catagory_id)->name }}
|
||||
</td>
|
||||
|
||||
@include('components.userfields_tbody', array(
|
||||
'userfields' => $userfields,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user