Add support for set number of recipe ingredient amounts

This commit is contained in:
Christopher C. Wells 2020-12-30 11:27:38 -08:00
parent a04be37acb
commit d4e3658294
10 changed files with 229 additions and 114 deletions

View File

@ -49,7 +49,7 @@ class IngredientController extends Controller
]); ]);
/** @var \App\Models\Ingredient $ingredient */ /** @var \App\Models\Ingredient $ingredient */
$ingredient = tap(new Ingredient(array_filter($attributes)))->save(); $ingredient = tap(new Ingredient(array_filter($attributes)))->save();
return redirect()->back()->with('message', "Ingredient {$ingredient->name} added!"); return back()->with('message', "Ingredient {$ingredient->name} added!");
} }
/** /**

View File

@ -2,9 +2,14 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Ingredient;
use App\Models\IngredientAmount;
use App\Models\Recipe; use App\Models\Recipe;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class RecipeController extends Controller class RecipeController extends Controller
{ {
@ -25,18 +30,72 @@ class RecipeController extends Controller
*/ */
public function create(): View public function create(): View
{ {
return view('recipes.create'); $ingredients = Ingredient::all(['id', 'name', 'detail'])->collect()
->map(function ($ingredient) {
return [
'value' => $ingredient->id,
'label' => "{$ingredient->name}" . ($ingredient->detail ? ", {$ingredient->detail}" : ""),
];
});
return view('recipes.create')
->with('ingredients', $ingredients)
->with('ingredient_units', new Collection([
['value' => 'tsp', 'label' => 'tsp.'],
['value' => 'tbsp', 'label' => 'tbsp.'],
['value' => 'cup', 'label' => 'cup'],
['value' => 'g', 'label' => 'g'],
]));
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response * @return \Illuminate\Http\RedirectResponse
*/ */
public function store(Request $request) public function store(Request $request): RedirectResponse
{ {
// $input = $request->validate([
'name' => 'required|string',
'description' => 'required|string',
'servings' => 'required|numeric',
'ingredients_amount' => 'required|array',
'ingredients_amount.*' => 'required|numeric|min:0',
'ingredients_unit' => 'required|array',
'ingredients_unit.*' => 'nullable|string',
'ingredients' => 'required|array',
'ingredients.*' => 'required|exists:App\Models\Ingredient,id',
]);
$recipe = new Recipe([
'name' => $input['name'],
'description' => $input['description'],
'servings' => (int) $input['servings'],
]);
try {
DB::transaction(function () use ($recipe, $input) {
if (!$recipe->save()) {
return;
}
$ingredient_amounts = [];
foreach ($input['ingredients_amount'] as $key => $amount) {
$ingredient_amounts[$key] = new IngredientAmount([
'amount' => (float) $amount,
'unit' => $input['ingredients_unit'][$key],
'weight' => (int) $key,
]);
$ingredient_amounts[$key]->recipe()->associate($recipe);
$ingredient_amounts[$key]->ingredient()->associate($input['ingredients'][$key]);
}
$recipe->ingredientAmounts()->saveMany($ingredient_amounts);
});
} catch (\Exception $e) {
DB::rollBack();
return back()->withInput()->withErrors("Failed to add recipe due to database error: {$e->getMessage()}.");
}
return back()->with('message', "Recipe {$recipe->name} added!");
} }
/** /**

View File

@ -0,0 +1,35 @@
<?php
namespace App\View\Components\Inputs;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Collection;
use Illuminate\View\Component;
class Select extends Component
{
public Collection $options;
public string $selectedValue;
/**
* Select constructor.
*
* @param \Illuminate\Support\Collection $options
* @param string $selectedValue
*/
public function __construct(
Collection $options,
string $selectedValue = '',
) {
$this->options = $options;
$this->selectedValue = $selectedValue;
}
public function render(): View
{
return view('components.inputs.select')
->with('options', $this->options)
->with('selectedValue', $this->selectedValue);
}
}

View File

@ -0,0 +1,9 @@
<select {{ $attributes->merge(['class' => 'rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50']) }}>
{{ $slot }}
@foreach ($options as $option)
<option value="{{ $option['value'] }}"
@if ($option['value'] === $selectedValue) selected @endif>
{{ $option['label'] }}
</option>
@endforeach
</select>

View File

@ -26,125 +26,125 @@
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200"> <div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ route('ingredients.store') }}"> <form method="POST" action="{{ route('ingredients.store') }}">
@csrf @csrf
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<!-- Name --> <!-- Name -->
<div> <div>
<x-label for="name" :value="__('Name')" /> <x-inputs.label for="name" :value="__('Name')"/>
<x-input id="name" <x-inputs.input id="name"
class="block mt-1 w-full" class="block mt-1 w-full"
type="text" type="text"
name="name" name="name"
:value="old('name')" :value="old('name')"
required /> required/>
</div> </div>
<!-- Detail --> <!-- Detail -->
<div> <div>
<x-label for="detail" :value="__('Detail')" /> <x-inputs.label for="detail" :value="__('Detail')"/>
<x-input id="detail" <x-inputs.input id="detail"
class="block mt-1 w-full" class="block mt-1 w-full"
type="text" type="text"
name="detail" name="detail"
:value="old('detail')" /> :value="old('detail')"/>
</div> </div>
</div> </div>
<div class="grid grid-rows-3 md:grid-rows-2 lg:grid-rows-1 grid-flow-col"> <div class="grid grid-rows-3 md:grid-rows-2 lg:grid-rows-1 grid-flow-col">
<!-- Calories --> <!-- Calories -->
<div> <div>
<x-label for="calories" :value="__('Calories')" /> <x-inputs.label for="calories" :value="__('Calories')"/>
<x-input id="calories" <x-inputs.input id="calories"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="calories" name="calories"
size="10" size="10"
:value="old('calories')" /> :value="old('calories')"/>
</div> </div>
<!-- Carbohydrates --> <!-- Carbohydrates -->
<div> <div>
<x-label for="carbohydrates" :value="__('Carbohydrates')" /> <x-inputs.label for="carbohydrates" :value="__('Carbohydrates')"/>
<x-input id="carbohydrates" <x-inputs.input id="carbohydrates"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="carbohydrates" name="carbohydrates"
size="10" size="10"
:value="old('carbohydrates')" /> :value="old('carbohydrates')"/>
</div> </div>
<!-- Cholesterol --> <!-- Cholesterol -->
<div> <div>
<x-label for="cholesterol" :value="__('Cholesterol')" /> <x-inputs.label for="cholesterol" :value="__('Cholesterol')"/>
<x-input id="cholesterol" <x-inputs.input id="cholesterol"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="cholesterol" name="cholesterol"
size="10" size="10"
:value="old('cholesterol')" /> :value="old('cholesterol')"/>
</div> </div>
<!-- Fat --> <!-- Fat -->
<div> <div>
<x-label for="fat" :value="__('Fat')" /> <x-inputs.label for="fat" :value="__('Fat')"/>
<x-input id="fat" <x-inputs.input id="fat"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="fat" name="fat"
size="10" size="10"
:value="old('fat')" /> :value="old('fat')"/>
</div> </div>
<!-- Protein --> <!-- Protein -->
<div> <div>
<x-label for="protein" :value="__('Protein')" /> <x-inputs.label for="protein" :value="__('Protein')"/>
<x-input id="protein" <x-inputs.input id="protein"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="protein" name="protein"
size="10" size="10"
:value="old('protein')" /> :value="old('protein')"/>
</div> </div>
<!-- Sodium --> <!-- Sodium -->
<div> <div>
<x-label for="sodium" :value="__('Sodium')" /> <x-inputs.label for="sodium" :value="__('Sodium')"/>
<x-input id="sodium" <x-inputs.input id="sodium"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="sodium" name="sodium"
size="10" size="10"
:value="old('sodium')" /> :value="old('sodium')"/>
</div> </div>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<!-- Cup weight --> <!-- Cup weight -->
<div> <div>
<x-label for="cup_weight" :value="__('Cup weight')" /> <x-inputs.label for="cup_weight" :value="__('Cup weight')"/>
<x-input id="cup_weight" <x-inputs.input id="cup_weight"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="cup_weight" name="cup_weight"
size="10" size="10"
:value="old('cup_weight')" /> :value="old('cup_weight')"/>
</div> </div>
<div class="p-4 font-black text-3xl"> <div class="p-4 font-black text-3xl">
@ -153,22 +153,22 @@
<!-- Unit weight --> <!-- Unit weight -->
<div> <div>
<x-label for="unit_weight" :value="__('Unit weight')" /> <x-inputs.label for="unit_weight" :value="__('Unit weight')"/>
<x-input id="unit_weight" <x-inputs.input id="unit_weight"
class="block mt-1" class="block mt-1"
type="number" type="number"
step="any" step="any"
name="unit_weight" name="unit_weight"
size="10" size="10"
:value="old('unit_weight')" /> :value="old('unit_weight')"/>
</div> </div>
</div> </div>
</div> </div>
<div class="flex items-center justify-end mt-4"> <div class="flex items-center justify-end mt-4">
<x-button class="ml-3"> <x-inputs.button class="ml-3">
{{ __('Add') }} {{ __('Add') }}
</x-button> </x-inputs.button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -25,56 +25,68 @@
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200"> <div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ route('ingredients.store') }}"> <form method="POST" action="{{ route('recipes.store') }}">
@csrf @csrf
<div class="flex flex-col space-y-4"> <div class="flex flex-col space-y-4">
<div class="grid grid-cols-5 gap-4"> <div class="grid grid-cols-5 gap-4">
<!-- Name --> <!-- Name -->
<div class="col-span-4"> <div class="col-span-4">
<x-label for="name" :value="__('Name')" /> <x-inputs.label for="name" :value="__('Name')" />
<x-input id="name" <x-inputs.input id="name"
class="block mt-1 w-full" class="block mt-1 w-full"
type="text" type="text"
name="name" name="name"
:value="old('name')" :value="old('name')"
required /> required />
</div> </div>
<!-- Servings --> <!-- Servings -->
<div> <div>
<x-label for="servings" :value="__('Servings')" /> <x-inputs.label for="servings" :value="__('Servings')" />
<x-input id="servings" <x-inputs.input id="servings"
class="block mt-1 w-full" class="block mt-1 w-full"
type="number" type="number"
name="servings" name="servings"
:value="old('servings')" /> :value="old('servings')" />
</div> </div>
</div> </div>
<!-- Description --> <!-- Description -->
<div> <div>
<x-label for="description" :value="__('Description')" /> <x-inputs.label for="description" :value="__('Description')" />
<x-form.textarea id="description" <x-inputs.textarea id="description"
class="block mt-1 w-full" class="block mt-1 w-full"
name="description" name="description"
:value="old('description')" /> :value="old('description')" />
</div> </div>
</div> </div>
<h3 class="pt-2 font-extrabold">Ingredients</h3>
<div class="flex flex-row space-x-4"> <!-- Ingredients -->
<x-input class="mt-1" <h3 class="pt-2 mb-2 font-extrabold">Ingredients</h3>
type="number" @for($i = 0; $i < 5; $i++)
name="ingredients_amount[]" <div class="flex flex-row space-x-4 mb-4">
step="any" <x-inputs.input type="number"
required /> name="ingredients_amount[]"
</div> step="any"
required />
<x-inputs.select name="ingredients_unit[]"
:options="$ingredient_units">
<option value=""></option>
</x-inputs.select>
<x-inputs.select name="ingredients[]"
:options="$ingredients"
required>
<option value=""></option>
</x-inputs.select>
</div>
@endfor
<div class="flex items-center justify-end mt-4"> <div class="flex items-center justify-end mt-4">
<x-button class="ml-3"> <x-inputs.button class="ml-3">
{{ __('Add') }} {{ __('Add') }}
</x-button> </x-inputs.button>
</div> </div>
</form> </form>
</div> </div>