mirror of https://github.com/kcal-app/kcal.git
Add support for recipe recipe ingredients
This commit is contained in:
parent
ff829d9d8d
commit
983b7695dd
|
|
@ -8,6 +8,7 @@ use App\Models\Recipe;
|
|||
use App\Models\RecipeStep;
|
||||
use App\Rules\ArrayNotEmpty;
|
||||
use App\Rules\StringIsDecimalOrFraction;
|
||||
use App\Rules\UsesIngredientTrait;
|
||||
use App\Support\Number;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
|
@ -83,6 +84,7 @@ class RecipeController extends Controller
|
|||
'amount' => $old['amount'][$key],
|
||||
'unit' => $old['unit'][$key],
|
||||
'ingredient_id' => $ingredient_id,
|
||||
'ingredient_type' => $old['type'][$key],
|
||||
'ingredient_name' => $old['name'][$key],
|
||||
'detail' => $old['detail'][$key],
|
||||
];
|
||||
|
|
@ -94,7 +96,8 @@ class RecipeController extends Controller
|
|||
'original_key' => $key,
|
||||
'amount' => $ingredientAmount->amount_formatted,
|
||||
'unit' => $ingredientAmount->unit,
|
||||
'ingredient_id' => $ingredientAmount->ingredient->id,
|
||||
'ingredient_id' => $ingredientAmount->ingredient_id,
|
||||
'ingredient_type' => $ingredientAmount->ingredient_type,
|
||||
'ingredient_name' => $ingredientAmount->ingredient->name,
|
||||
'detail' => $ingredientAmount->detail,
|
||||
];
|
||||
|
|
@ -160,12 +163,22 @@ class RecipeController extends Controller
|
|||
'ingredients.detail.*' => 'nullable|string',
|
||||
'ingredients.id' => ['required', 'array', new ArrayNotEmpty],
|
||||
'ingredients.id.*' => 'required_with:ingredients.amount.*|nullable',
|
||||
'ingredients.type' => ['required', 'array', new ArrayNotEmpty],
|
||||
'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()],
|
||||
'ingredients.original_key' => 'nullable|array',
|
||||
'steps.step' => ['required', 'array', new ArrayNotEmpty],
|
||||
'steps.step.*' => 'nullable|string',
|
||||
'steps.original_key' => 'nullable|array',
|
||||
]);
|
||||
|
||||
// Validate that no ingredients are recursive.
|
||||
// TODO: refactor as custom validator.
|
||||
foreach (array_filter($input['ingredients']['id']) as $key => $id) {
|
||||
if ($input['ingredients']['type'][$key] == Recipe::class && $id == $recipe->id) {
|
||||
return back()->withInput()->withErrors('To understand recursion, you must understand recursion. Remove this recipe from this recipe.');
|
||||
}
|
||||
}
|
||||
|
||||
$recipe->fill([
|
||||
'name' => Str::lower($input['name']),
|
||||
'description' => $input['description'],
|
||||
|
|
@ -202,7 +215,7 @@ class RecipeController extends Controller
|
|||
'weight' => $weight++,
|
||||
]);
|
||||
$ingredient_amounts[$key]->ingredient()
|
||||
->associate(Food::where('id', $ingredient_id)->first());
|
||||
->associate($input['ingredients']['type'][$key]::where('id', $ingredient_id)->first());
|
||||
}
|
||||
$recipe->ingredientAmounts()->saveMany($ingredient_amounts);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,11 +78,6 @@ class Recipe extends Model
|
|||
'sodiumPerServing',
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $with = ['ingredientAmounts'];
|
||||
|
||||
/**
|
||||
* Get the steps for this Recipe.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -8,6 +8,22 @@ use Illuminate\Support\Collection;
|
|||
|
||||
trait Ingredient
|
||||
{
|
||||
/**
|
||||
* Add special `type` attribute to appends.
|
||||
*/
|
||||
public function initializeIngredient(): void {
|
||||
$this->appends[] = 'type';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class name.
|
||||
*
|
||||
* This is necessary e.g. to provide data in ingredient picker responses.
|
||||
*/
|
||||
public function getTypeAttribute(): string {
|
||||
return $this::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the ingredient amounts associated with the ingredient.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Models\Traits\Ingredient;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class UsesIngredientTrait implements Rule
|
||||
{
|
||||
/**
|
||||
* Determine if the array is empty.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
return (
|
||||
class_exists($value)
|
||||
&& in_array(Ingredient::class, class_uses_recursive($value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message(): string
|
||||
{
|
||||
return 'Invalid ingredient type :input.';
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,10 @@
|
|||
name="ingredients[id][]"
|
||||
value="{{ $defaultId ?? '' }}"
|
||||
x-ref="ingredients"/>
|
||||
<x-inputs.input type="hidden"
|
||||
name="ingredients[type][]"
|
||||
value="{{ $defaultType ?? '' }}"
|
||||
x-ref="ingredients_type"/>
|
||||
<x-inputs.input type="text"
|
||||
name="ingredients[name][]"
|
||||
value="{{ $defaultName ?? '' }}"
|
||||
|
|
@ -19,9 +23,12 @@
|
|||
<template x-for="result in results" :key="result.id">
|
||||
<div class="p-1 border-b-2 border-gray-500 hover:bg-yellow-300 cursor-pointer"
|
||||
x-bind:data-id="result.id"
|
||||
x-bind:data-value="result.name">
|
||||
x-bind:data-type="result.type"
|
||||
x-bind:data-name="result.name">
|
||||
<div class="pointer-events-none">
|
||||
<div x-text="result.name"></div>
|
||||
<div>
|
||||
<span x-text="result.name"></span><span class="text-gray-600" x-text="', ' + result.detail" x-show="result.detail"></span>
|
||||
</div>
|
||||
<div x-show="result.serving_size">
|
||||
<div class="text-sm text-gray-600" x-text="result.brand" x-show="result.brand"></div>
|
||||
<div class="text-sm">
|
||||
|
|
@ -54,7 +61,10 @@
|
|||
if ($event.target.value !== '') {
|
||||
fetch('{{ route('ingredient-picker.search') }}?term=' + $event.target.value)
|
||||
.then(response => response.json())
|
||||
.then(data => { this.results = data; this.searching = true; });
|
||||
.then(data => {
|
||||
this.results = data;
|
||||
this.searching = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
['@focusout.debounce.200ms']() {
|
||||
|
|
@ -65,8 +75,9 @@
|
|||
['@click']($event) {
|
||||
let selected = $event.target;
|
||||
if (selected.dataset.id) {
|
||||
this.$refs.ingredients_name.value = selected.dataset.value;
|
||||
this.$refs.ingredients.value = selected.dataset.id;
|
||||
this.$refs.ingredients_type.value = selected.dataset.type;
|
||||
this.$refs.ingredients_name.value = selected.dataset.name;
|
||||
this.searching = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<option value=""></option>
|
||||
</x-inputs.select>
|
||||
<x-ingredient-picker :default-id="$ingredient_id ?? null"
|
||||
:default-type="$ingredient_type ?? null"
|
||||
:default-name="$ingredient_name ?? null" />
|
||||
<x-inputs.input type="text"
|
||||
class="block"
|
||||
|
|
|
|||
Loading…
Reference in New Issue