mirror of https://github.com/kcal-app/kcal.git
Add support for set number of recipe ingredient amounts
This commit is contained in:
parent
a04be37acb
commit
d4e3658294
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue