mirror of https://github.com/kcal-app/kcal.git
Add recipe edit support (WIP)
Not possible to remove existing ingredients or steps.
This commit is contained in:
parent
f3f2dd8b6f
commit
ab28efba52
|
@ -35,85 +35,20 @@ class RecipeController extends Controller
|
|||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$foods = Food::all(['id', 'name', 'detail'])->sortBy('name')->collect()
|
||||
->map(function ($food) {
|
||||
return [
|
||||
'value' => $food->id,
|
||||
'label' => "{$food->name}" . ($food->detail ? ", {$food->detail}" : ""),
|
||||
];
|
||||
});
|
||||
return view('recipes.create')
|
||||
->with('foods', $foods)
|
||||
->with('food_units', new Collection([
|
||||
['value' => 'tsp', 'label' => 'tsp.'],
|
||||
['value' => 'tbsp', 'label' => 'tbsp.'],
|
||||
['value' => 'cup', 'label' => 'cup'],
|
||||
['value' => 'grams', 'label' => 'g'],
|
||||
]));
|
||||
return $this->edit(new Recipe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$input = $request->validate([
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'servings' => 'required|numeric',
|
||||
'foods_amount' => ['required', 'array', new ArrayNotEmpty],
|
||||
'foods_amount.*' => ['required_with:foods.*', 'nullable', new StringIsDecimalOrFraction],
|
||||
'foods_unit' => ['required', 'array'],
|
||||
'foods_unit.*' => 'nullable|string',
|
||||
'foods' => ['required', 'array', new ArrayNotEmpty],
|
||||
'foods.*' => 'required_with:foods_amount.*|nullable|exists:App\Models\Food,id',
|
||||
'steps' => ['required', 'array', new ArrayNotEmpty],
|
||||
'steps.*' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$recipe = new Recipe([
|
||||
'name' => $input['name'],
|
||||
'description' => $input['description'],
|
||||
'servings' => (int) $input['servings'],
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($recipe, $input) {
|
||||
if (!$recipe->save()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$food_amounts = [];
|
||||
$weight = 0;
|
||||
foreach (array_filter($input['foods_amount']) as $key => $amount) {
|
||||
$food_amounts[$key] = new FoodAmount([
|
||||
'amount' => Number::floatFromString($amount),
|
||||
'unit' => $input['foods_unit'][$key],
|
||||
'weight' => $weight++,
|
||||
]);
|
||||
$food_amounts[$key]->food()->associate($input['foods'][$key]);
|
||||
}
|
||||
$recipe->foodAmounts()->saveMany($food_amounts);
|
||||
|
||||
$steps = [];
|
||||
$number = 1;
|
||||
foreach (array_filter($input['steps']) as $step) {
|
||||
$steps[] = new RecipeStep([
|
||||
'number' => $number++,
|
||||
'step' => $step,
|
||||
]);
|
||||
}
|
||||
$recipe->foodAmounts()->saveMany($steps);
|
||||
});
|
||||
} 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!");
|
||||
return $this->update($request, new Recipe());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,24 +65,97 @@ class RecipeController extends Controller
|
|||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Recipe $recipe
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param \App\Models\Recipe $recipe
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Recipe $recipe)
|
||||
public function edit(Recipe $recipe): View
|
||||
{
|
||||
//
|
||||
$foods = Food::all(['id', 'name', 'detail'])->sortBy('name')->collect()
|
||||
->map(function ($food) {
|
||||
return [
|
||||
'value' => $food->id,
|
||||
'label' => "{$food->name}" . ($food->detail ? ", {$food->detail}" : ""),
|
||||
];
|
||||
});
|
||||
return view('recipes.edit')
|
||||
->with('recipe', $recipe)
|
||||
->with('foods', $foods)
|
||||
->with('food_units', new Collection([
|
||||
['value' => 'tsp', 'label' => 'tsp.'],
|
||||
['value' => 'tbsp', 'label' => 'tbsp.'],
|
||||
['value' => 'cup', 'label' => 'cup'],
|
||||
['value' => 'oz', 'label' => 'oz'],
|
||||
['value' => 'grams', 'label' => 'g'],
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Recipe $recipe
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Recipe $recipe
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function update(Request $request, Recipe $recipe)
|
||||
public function update(Request $request, Recipe $recipe): RedirectResponse
|
||||
{
|
||||
//
|
||||
$input = $request->validate([
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'servings' => 'required|numeric',
|
||||
'foods_amount' => ['required', 'array', new ArrayNotEmpty],
|
||||
'foods_amount.*' => ['required_with:foods.*', 'nullable', new StringIsDecimalOrFraction],
|
||||
'foods_unit' => ['required', 'array'],
|
||||
'foods_unit.*' => 'nullable|string',
|
||||
'foods' => ['required', 'array', new ArrayNotEmpty],
|
||||
'foods.*' => 'required_with:foods_amount.*|nullable|exists:App\Models\Food,id',
|
||||
'steps' => ['required', 'array', new ArrayNotEmpty],
|
||||
'steps.*' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$recipe->fill([
|
||||
'name' => $input['name'],
|
||||
'description' => $input['description'],
|
||||
'servings' => (int) $input['servings'],
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($recipe, $input) {
|
||||
if (!$recipe->save()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$food_amounts = [];
|
||||
$weight = 0;
|
||||
// TODO: Handle removals.
|
||||
foreach (array_filter($input['foods_amount']) as $key => $amount) {
|
||||
$food_amounts[$key] = $recipe->foodAmounts[$key] ?? new FoodAmount();
|
||||
$food_amounts[$key]->fill([
|
||||
'amount' => Number::floatFromString($amount),
|
||||
'unit' => $input['foods_unit'][$key],
|
||||
'weight' => $weight++,
|
||||
]);
|
||||
$food_amounts[$key]->food()->associate($input['foods'][$key]);
|
||||
}
|
||||
$recipe->foodAmounts()->saveMany($food_amounts);
|
||||
|
||||
$steps = [];
|
||||
$number = 1;
|
||||
// TODO: Handle removals.
|
||||
foreach (array_filter($input['steps']) as $key => $step) {
|
||||
$steps[$key] = $recipe->steps[$key] ?? new RecipeStep();
|
||||
$steps[$key]->fill(['number' => $number++, 'step' => $step]);
|
||||
}
|
||||
$recipe->foodAmounts()->saveMany($steps);
|
||||
});
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withInput()->withErrors("Failed to updated recipe due to database error: {$e->getMessage()}.");
|
||||
}
|
||||
|
||||
return back()->with('message', "Recipe {$recipe->name} updated!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ ($recipe->exists ? "Edit {$recipe->name}" : 'Add Recipe') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session()->has('message'))
|
||||
<div class="bg-green-200 border-2 border-green-600 p-2 mb-2">
|
||||
{{ session()->get('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="flex flex-col space-y-2 pb-2">
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="bg-red-200 border-2 border-red-900 p-2">
|
||||
{{ $error }}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 bg-white border-b border-gray-200">
|
||||
<form method="POST" action="{{ ($recipe->exists ? route('recipes.update', $recipe) : route('recipes.store')) }}">
|
||||
@if ($recipe->exists)@method('put')@endif
|
||||
@csrf
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<!-- Name -->
|
||||
<div class="col-span-4">
|
||||
<x-inputs.label for="name" :value="__('Name')" />
|
||||
|
||||
<x-inputs.input id="name"
|
||||
class="block mt-1 w-full"
|
||||
type="text"
|
||||
name="name"
|
||||
:value="old('name', $recipe->name)"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<!-- Servings -->
|
||||
<div>
|
||||
<x-inputs.label for="servings" :value="__('Servings')" />
|
||||
|
||||
<x-inputs.input id="servings"
|
||||
class="block mt-1 w-full"
|
||||
type="number"
|
||||
name="servings"
|
||||
:value="old('servings', $recipe->servings)"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<x-inputs.label for="description" :value="__('Description')" />
|
||||
|
||||
<x-inputs.textarea id="description"
|
||||
class="block mt-1 w-full"
|
||||
name="description"
|
||||
:value="old('description', $recipe->description)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ingredients -->
|
||||
<h3 class="pt-2 mb-2 font-extrabold">Ingredients</h3>
|
||||
@for($i = 0; $i < 20; $i++)
|
||||
@php
|
||||
if (isset($recipe->foodAmounts[$i])) {
|
||||
$foodAmount = $recipe->foodAmounts[$i];
|
||||
$amount = \App\Support\Number::fractionStringFromFloat($foodAmount->amount);
|
||||
$unit = $foodAmount->unit;
|
||||
$food_id = $foodAmount->food->id;
|
||||
} else {
|
||||
$foodAmount = new \App\Models\FoodAmount();
|
||||
$amount = $food_id = $unit = null;
|
||||
}
|
||||
@endphp
|
||||
<div class="flex flex-row space-x-4 mb-4">
|
||||
<x-inputs.input type="text"
|
||||
name="foods_amount[]"
|
||||
size="5"
|
||||
:value="old('foods_amount.' . $i, $amount)" />
|
||||
<x-inputs.select name="foods_unit[]"
|
||||
:options="$food_units"
|
||||
:selectedValue="old('foods_unit.' . $i, $unit)">
|
||||
<option value=""></option>
|
||||
</x-inputs.select>
|
||||
<x-inputs.select name="foods[]"
|
||||
:options="$foods"
|
||||
:selectedValue="old('foods.' . $i, $food_id)">
|
||||
<option value=""></option>
|
||||
</x-inputs.select>
|
||||
</div>
|
||||
@endfor
|
||||
|
||||
<!-- Steps -->
|
||||
<h3 class="pt-2 mb-2 font-extrabold">Steps</h3>
|
||||
@for($i = 0; $i < 20; $i++)
|
||||
@php($step = $recipe->steps[$i] ?? new \App\Models\RecipeStep())
|
||||
<div class="flex flex-row space-x-4 mb-4">
|
||||
<div class="text-3xl text-gray-400 text-center">{{ $i + 1 }}</div>
|
||||
<x-inputs.textarea class="block mt-1 w-full"
|
||||
name="steps[]"
|
||||
:value="old('steps.' . $i, $step->step)" />
|
||||
</div>
|
||||
@endfor
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-inputs.button class="ml-3">
|
||||
{{ ($recipe->exists ? 'Save' : 'Add') }}
|
||||
</x-inputs.button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
|
@ -17,6 +17,13 @@
|
|||
<div class="grid grid-cols-3 gap-4">
|
||||
@foreach ($recipes as $recipe)
|
||||
<div class="p-2 font-light rounded-lg border-2 border-gray-200">
|
||||
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300 float-right text-sm"
|
||||
href="{{ route('recipes.edit', $recipe) }}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
<div class="pb-2 lowercase flex justify-between items-baseline">
|
||||
<div class="text-2xl">
|
||||
<a href="{{ route('recipes.show', $recipe) }}"
|
||||
|
|
Loading…
Reference in New Issue