Start working on recipes feature

This commit is contained in:
Bernd Bestel 2018-07-14 18:23:41 +02:00
parent 70e7e630c3
commit d9246b9b42
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
17 changed files with 624 additions and 4 deletions

View File

@ -11,13 +11,15 @@ class BaseController
public function __construct(\Slim\Container $container) { public function __construct(\Slim\Container $container) {
$databaseService = new DatabaseService(); $databaseService = new DatabaseService();
$this->Database = $databaseService->GetDbConnection(); $this->Database = $databaseService->GetDbConnection();
$localizationService = new LocalizationService(CULTURE);
$this->LocalizationService = $localizationService;
$applicationService = new ApplicationService(); $applicationService = new ApplicationService();
$versionInfo = $applicationService->GetInstalledVersion(); $versionInfo = $applicationService->GetInstalledVersion();
$container->view->set('version', $versionInfo->Version); $container->view->set('version', $versionInfo->Version);
$container->view->set('releaseDate', $versionInfo->ReleaseDate); $container->view->set('releaseDate', $versionInfo->ReleaseDate);
$localizationService = new LocalizationService(CULTURE);
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations()); $container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService) $container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
{ {
@ -33,4 +35,5 @@ class BaseController
protected $AppContainer; protected $AppContainer;
protected $Database; protected $Database;
protected $LocalizationService;
} }

View File

@ -0,0 +1,70 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
class RecipesController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
}
protected $RecipesService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'recipes', [
'recipes' => $this->Database->recipes()->orderBy('name'),
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment()
]);
}
public function RecipeEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$recipeId = $args['recipeId'];
if ($recipeId == 'new')
{
$newRecipe = $this->Database->recipes()->createRow(array(
'name' => $this->LocalizationService->Localize('New recipe')
));
$newRecipe->save();
$recipeId = $this->Database->lastInsertId();
}
return $this->AppContainer->view->render($response, 'recipeform', [
'recipe' => $this->Database->recipes($recipeId),
'recipePositions' => $this->Database->recipes_pos()->where('recipe_id', $recipeId),
'mode' => 'edit',
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment()
]);
}
public function RecipePosEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['recipePosId'] == 'new')
{
return $this->AppContainer->view->render($response, 'recipeposform', [
'mode' => 'create',
'recipe' => $this->Database->recipes($args['recipeId']),
'products' => $this->Database->products()
]);
}
else
{
return $this->AppContainer->view->render($response, 'recipeposform', [
'mode' => 'edit',
'recipe' => $this->Database->recipes($args['recipeId']),
'recipePos' => $this->Database->recipes_pos($args['recipePosId']),
'products' => $this->Database->products()
]);
}
}
}

View File

@ -894,7 +894,9 @@
"batteries", "batteries",
"locations", "locations",
"quantity_units", "quantity_units",
"shopping_list" "shopping_list",
"recipes",
"recipes_pos"
] ]
}, },
"StockTransactionType": { "StockTransactionType": {

View File

@ -145,6 +145,14 @@ return array(
'Settings' => 'Einstellungen', 'Settings' => 'Einstellungen',
'This can only be before now' => 'Dies kann nur vor jetzt sein', 'This can only be before now' => 'Dies kann nur vor jetzt sein',
'Calendar' => 'Kalender', 'Calendar' => 'Kalender',
'Recipes' => 'Rezepte',
'Edit recipe' => 'Rezept bearbeiten',
'New recipe' => 'Neues Rezept',
'Ingredients list' => 'Zutatenliste',
'Add recipe ingredient' => 'Rezeptzutat hinzufügen',
'Edit recipe ingredient' => 'Rezeptzutat bearbeiten',
'Are you sure to delete recipe "#1"?' => 'Rezept "#1" wirklich löschen?',
'Are you sure to delete recipe ingredient "#1"?' => 'Rezeptzutat "#1" wirklich löschen?',
//Constants //Constants
'manually' => 'Manuell', 'manually' => 'Manuell',

38
migrations/0025.sql Normal file
View File

@ -0,0 +1,38 @@
CREATE TABLE recipes (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE TABLE recipes_pos (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
recipe_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL DEFAULT 0,
note TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE VIEW recipes_fulfillment
AS
SELECT
r.id AS recipe_id,
rp.id AS recipe_pos_id,
rp.product_id,
rp.amount AS recipe_amount,
sc.amount AS stock_amount,
CASE WHEN sc.amount >= rp.amount THEN 1 ELSE 0 END AS need_fullfiled
FROM recipes r
LEFT JOIN recipes_pos rp
ON r.id = rp.recipe_id
LEFT JOIN stock_current sc
ON rp.product_id = sc.product_id;
CREATE VIEW recipes_fulfillment_sum
AS
SELECT
rf.recipe_id,
MIN(rf.need_fullfiled) AS need_fullfiled
FROM recipes_fulfillment rf
GROUP BY rf.recipe_id;

View File

@ -49,6 +49,11 @@ $('.combobox').combobox({
}); });
var prefillProduct = GetUriParam('createdproduct'); var prefillProduct = GetUriParam('createdproduct');
var prefillProduct2 = Grocy.Components.ProductPicker.GetPicker().parent().data('prefill-by-name').toString();
if (!prefillProduct2.isEmpty())
{
prefillProduct = prefillProduct2;
}
if (typeof prefillProduct !== "undefined") if (typeof prefillProduct !== "undefined")
{ {
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first(); var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();

View File

@ -0,0 +1,98 @@
$('#save-recipe-button').on('click', function(e)
{
e.preventDefault();
Grocy.Api.Post('edit-object/recipes/' + Grocy.EditObjectId, $('#recipe-form').serializeJSON(),
function(result)
{
window.location.href = U('/recipes');
},
function(xhr)
{
console.error(xhr);
}
);
});
var recipesPosTables = $('#recipes-pos-table').DataTable({
'paginate': false,
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true
});
$("#search").on("keyup", function ()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
recipesPosTables.search(value).draw();
});
Grocy.FrontendHelpers.ValidateForm('recipe-form');
$("#name").focus();
$('#recipe-form input').keyup(function (event)
{
Grocy.FrontendHelpers.ValidateForm('recipe-form');
});
$('#recipe-form input').keydown(function (event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('recipe-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-recipe-button').click();
}
}
});
$(document).on('click', '.recipe-pos-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-recipe-pos-name');
var objectId = $(e.currentTarget).attr('data-recipe-pos-id');
bootbox.confirm({
message: L('Are you sure to delete recipe ingredient "#1"?', objectName),
buttons: {
confirm: {
label: L('Yes'),
className: 'btn-success'
},
cancel: {
label: L('No'),
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
Grocy.Api.Get('delete-object/recipes_pos/' + objectId,
function(result)
{
window.location.href = U('/recipe/' + Grocy.EditObjectId);
},
function(xhr)
{
console.error(xhr);
}
);
}
}
});
});

View File

@ -0,0 +1,97 @@
$('#save-recipe-pos-button').on('click', function(e)
{
e.preventDefault();
var jsonData = $('#recipe-pos-form').serializeJSON();
jsonData.recipe_id = Grocy.EditObjectParentId;
console.log(jsonData);
if (Grocy.EditMode === 'create')
{
Grocy.Api.Post('add-object/recipes_pos', jsonData,
function(result)
{
window.location.href = U('/recipe/' + Grocy.EditObjectParentId);
},
function(xhr)
{
console.error(xhr);
}
);
}
else
{
Grocy.Api.Post('edit-object/recipes_pos/' + Grocy.EditObjectId, jsonData,
function(result)
{
window.location.href = U('/recipe/' + Grocy.EditObjectParentId);
},
function(xhr)
{
console.error(xhr);
}
);
}
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
var productId = $(e.target).val();
if (productId)
{
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/get-product-details/' + productId,
function (productDetails)
{
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
$('#amount').focus();
},
function(xhr)
{
console.error(xhr);
}
);
}
});
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
if (Grocy.Components.ProductPicker.InProductAddWorkflow() === false)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
Grocy.Components.ProductPicker.GetPicker().trigger('change');
$('#amount').on('focus', function(e)
{
if (Grocy.Components.ProductPicker.GetValue().length === 0)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
$(this).select();
}
});
$('#recipe-pos-form input').keyup(function (event)
{
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
});
$('#recipe-pos-form input').keydown(function (event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('recipe-pos-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-recipe-pos-button').click();
}
}
});

58
public/viewjs/recipes.js Normal file
View File

@ -0,0 +1,58 @@
var recipesTables = $('#recipes-table').DataTable({
'paginate': false,
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true
});
$("#search").on("keyup", function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
recipesTables.search(value).draw();
});
$(document).on('click', '.recipe-delete-button', function(e)
{
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
bootbox.confirm({
message: L('Are you sure to delete recipe "#1"?', objectName),
buttons: {
confirm: {
label: L('Yes'),
className: 'btn-success'
},
cancel: {
label: L('No'),
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
Grocy.Api.Get('delete-object/recipes/' + objectId,
function(result)
{
window.location.href = U('/recipes');
},
function(xhr)
{
console.error(xhr);
}
);
}
}
});
});

View File

@ -34,6 +34,11 @@ $app->group('', function()
$this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList'); $this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList');
$this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm'); $this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm');
// Recipe routes
$this->get('/recipes', 'Grocy\Controllers\RecipesController:Overview');
$this->get('/recipe/{recipeId}', 'Grocy\Controllers\RecipesController:RecipeEditForm');
$this->get('/recipe/{recipeId}/pos/{recipePosId}', 'Grocy\Controllers\RecipesController:RecipePosEditForm');
// Habit routes // Habit routes
$this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview'); $this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview');
$this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution'); $this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution');
@ -41,7 +46,7 @@ $app->group('', function()
$this->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList'); $this->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList');
$this->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm'); $this->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm');
// Batterry routes // Battery routes
$this->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview'); $this->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview');
$this->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle'); $this->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle');

View File

@ -0,0 +1,18 @@
<?php
namespace Grocy\Services;
class RecipesService extends BaseService
{
public function GetRecipesFulfillment()
{
$sql = 'SELECT * from recipes_fulfillment';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetRecipesSumFulfillment()
{
$sql = 'SELECT * from recipes_fulfillment_sum';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
}

View File

@ -3,8 +3,9 @@
@endpush @endpush
@php if(empty($disallowAddProductWorkflows)) { $disallowAddProductWorkflows = false; } @endphp @php if(empty($disallowAddProductWorkflows)) { $disallowAddProductWorkflows = false; } @endphp
@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}" data-disallow-add-product-workflows="{{ BoolToString($disallowAddProductWorkflows) }}"> <div class="form-group" data-next-input-selector="{{ $nextInputSelector }}" data-disallow-add-product-workflows="{{ BoolToString($disallowAddProductWorkflows) }}" data-prefill-by-name="{{ $prefillByName }}">
<label for="product_id">{{ $L('Product') }} <i class="fas fa-barcode"></i><span id="barcode-lookup-disabled-hint" class="small text-muted d-none"> Barcode lookup is disabled</span></label> <label for="product_id">{{ $L('Product') }} <i class="fas fa-barcode"></i><span id="barcode-lookup-disabled-hint" class="small text-muted d-none"> Barcode lookup is disabled</span></label>
<select class="form-control combobox" id="product_id" name="product_id" required> <select class="form-control combobox" id="product_id" name="product_id" required>
<option value=""></option> <option value=""></option>

View File

@ -69,6 +69,12 @@
<span class="nav-link-text">{{ $L('Batteries overview') }}</span> <span class="nav-link-text">{{ $L('Batteries overview') }}</span>
</a> </a>
</li> </li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Recipes') }}" data-nav-for-page="recipes">
<a class="nav-link discrete-link" href="{{ $U('/recipes') }}">
<i class="fas fa-lemon"></i>
<span class="nav-link-text">{{ $L('Recipes') }}</span>
</a>
</li>
<li class="nav-item mt-4" data-toggle="tooltip" data-placement="right" title="{{ $L('Purchase') }}" data-nav-for-page="purchase"> <li class="nav-item mt-4" data-toggle="tooltip" data-placement="right" title="{{ $L('Purchase') }}" data-nav-for-page="purchase">
<a class="nav-link discrete-link" href="{{ $U('/purchase') }}"> <a class="nav-link discrete-link" href="{{ $U('/purchase') }}">

View File

@ -0,0 +1,93 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $L('Edit recipe'))
@else
@section('title', $L('Create recipe'))
@endif
@section('viewJsName', 'recipeform')
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<script>Grocy.EditMode = '{{ $mode }}';</script>
@if($mode == 'edit')
<script>Grocy.EditObjectId = {{ $recipe->id }};</script>
@endif
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-6 pb-3">
<form id="recipe-form" novalidate>
<div class="form-group">
<label for="name">{{ $L('Name') }}</label>
<input type="text" class="form-control" required id="name" name="name" value="@if($mode == 'edit'){{ $recipe->name }}@endif">
<div class="invalid-feedback">{{ $L('A name is required') }}</div>
</div>
<div class="form-group">
<label for="description">{{ $L('Description') }}</label>
<textarea id="description" class="form-control" name="description" rows="25">@if($mode == 'edit'){{ $recipe->description }}@endif</textarea>
</div>
<button id="save-recipe-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button>
</form>
</div>
<div class="col-xs-12 col-md-6 pb-3">
<h2>
{{ $L('Ingredients list') }}
<a class="btn btn-outline-dark" href="{{ $U('/recipe/' . $recipe->id . '/pos/new') }}">
<i class="fas fa-plus"></i> {{ $L('Add') }}
</a>
</h1>
<table id="recipes-pos-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Product') }}</th>
<th>{{ $L('Amount') }}</th>
<th>{{ $L('Note') }}</th>
</tr>
</thead>
<tbody>
@if($mode == "edit")
@foreach($recipePositions as $recipePosition)
<tr class="@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fullfiled == 1) table-success @endif">
<td class="fit-content">
<a class="btn btn-sm btn-info" href="{{ $U('/recipe/' . $recipe->id . '/pos/' . $recipePosition->id) }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-pos-delete-button" href="#" data-recipe-pos-id="{{ $recipePosition->id }}" data-recipe-pos-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
</td>
<td>
{{ $recipePosition->amount }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->qu_id_stock)->name }}
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fullfiled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock') }} @endif</span>
</td>
<td>
@if(strlen($recipePosition->note) > 50)
{{ substr($recipePosition->note, 0, 50) }}...
@else
{{ $recipePosition->note }}
@endif
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
@stop

View File

@ -0,0 +1,55 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $L('Edit recipe ingredient'))
@else
@section('title', $L('Add recipe ingredient'))
@endif
@section('viewJsName', 'recipeposform')
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<h3 class="text-muted">{{ $L('Recipe') }} <strong>{{ $recipe->name }}</strong></h3>
<script>
Grocy.EditMode = '{{ $mode }}';
Grocy.EditObjectParentId = {{ $recipe->id }};
</script>
@if($mode == 'edit')
<script>Grocy.EditObjectId = {{ $recipePos->id }};</script>
@endif
<form id="recipe-pos-form" novalidate>
@php $prefillByName = ''; if($mode=='edit') { $prefillByName = FindObjectInArrayByPropertyValue($products, 'id', $recipePos->product_id)->name; } @endphp
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#amount',
'prefillByName' => $prefillByName
))
<div class="form-group">
<label for="amount">{{ $L('Amount') }}&nbsp;&nbsp;<span id="amount_qu_unit" class="small text-muted"></span></label>
<input type="number" class="form-control" id="amount" name="amount" value="@if($mode == 'edit'){{ $recipePos->amount }}@else{{1}}@endif" min="0" required>
<div class="invalid-feedback">{{ $L('This cannot be negative') }}</div>
</div>
<div class="form-group">
<label for="note">{{ $L('Note') }}</label>
<textarea class="form-control" rows="2" id="note" name="note">@if($mode == 'edit'){{ $recipePos->note }}@endif</textarea>
</div>
<button id="save-recipe-pos-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button>
</form>
</div>
<div class="col-xs-12 col-md-6 col-xl-4">
@include('components.productcard')
</div>
</div>
@stop

59
views/recipes.blade.php Normal file
View File

@ -0,0 +1,59 @@
@extends('layout.default')
@section('title', $L('Recipes'))
@section('activeNav', 'recipes')
@section('viewJsName', 'recipes')
@section('content')
<div class="row">
<div class="col">
<h1>
@yield('title')
<a class="btn btn-outline-dark" href="{{ $U('/recipe/new') }}">
<i class="fas fa-plus"></i> {{ $L('Add') }}
</a>
</h1>
</div>
</div>
<div class="row mt-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
<input type="text" class="form-control" id="search">
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<table id="recipes-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Name') }}</th>
<th>{{ $L('Requirements fulfilled') }}</th>
</tr>
</thead>
<tbody>
@foreach($recipes as $recipe)
<tr class="@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fullfiled == 1) table-success @endif">
<td class="fit-content">
<a class="btn btn-sm btn-info" href="{{ $U('/recipe/') }}{{ $recipe->id }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-delete-button" href="#" data-recipe-id="{{ $recipe->id }}" data-recipe-name="{{ $recipe->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $recipe->name }}
</td>
<td>
@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fullfiled == 1){{ $L('Yes') }}@else{{ $L('No') }}@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -169,6 +169,10 @@ startbootstrap-sb-admin@^4.0.0:
jquery "3.3.1" jquery "3.3.1"
jquery.easing "^1.4.1" jquery.easing "^1.4.1"
summernote@^0.8.10:
version "0.8.10"
resolved "https://registry.yarnpkg.com/summernote/-/summernote-0.8.10.tgz#21a5d7f18a3b07500b58b60d5907417a54897520"
swagger-ui-dist@^3.17.3: swagger-ui-dist@^3.17.3:
version "3.17.3" version "3.17.3"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz#dfb96408ccc46775155f7369190c5d4b2016fe5c" resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz#dfb96408ccc46775155f7369190c5d4b2016fe5c"