mirror of https://github.com/kcal-app/kcal.git
Improve error messages in recipe update
This commit is contained in:
parent
c9ef13a0d4
commit
dbee32dc14
|
|
@ -2,19 +2,16 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\UpdateRecipeRequest;
|
||||||
use App\Models\Food;
|
use App\Models\Food;
|
||||||
use App\Models\IngredientAmount;
|
use App\Models\IngredientAmount;
|
||||||
use App\Models\Recipe;
|
use App\Models\Recipe;
|
||||||
use App\Models\RecipeSeparator;
|
use App\Models\RecipeSeparator;
|
||||||
use App\Models\RecipeStep;
|
use App\Models\RecipeStep;
|
||||||
use App\Rules\ArrayNotEmpty;
|
|
||||||
use App\Rules\StringIsDecimalOrFraction;
|
|
||||||
use App\Rules\UsesIngredientTrait;
|
|
||||||
use App\Support\Number;
|
use App\Support\Number;
|
||||||
use App\Support\Nutrients;
|
use App\Support\Nutrients;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
@ -44,12 +41,9 @@ class RecipeController extends Controller
|
||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(UpdateRecipeRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
return $this->update($request, new Recipe());
|
return $this->update($request, new Recipe());
|
||||||
}
|
}
|
||||||
|
|
@ -187,50 +181,11 @@ class RecipeController extends Controller
|
||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \App\Models\Recipe $recipe
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
*
|
|
||||||
* @throws \Throwable
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, Recipe $recipe): RedirectResponse
|
public function update(UpdateRecipeRequest $request, Recipe $recipe): RedirectResponse
|
||||||
{
|
{
|
||||||
$input = $request->validate([
|
$input = $request->validated();
|
||||||
'name' => ['required', 'string'],
|
|
||||||
'description' => ['nullable', 'string'],
|
|
||||||
'description_delta' => ['nullable', 'string'],
|
|
||||||
'image' => ['nullable', 'file', 'mimes:jpg,png,gif'],
|
|
||||||
'remove_image' => ['nullable', 'boolean'],
|
|
||||||
'servings' => ['required', 'numeric'],
|
|
||||||
'time_prep' => ['nullable', 'numeric'],
|
|
||||||
'time_cook' => ['nullable', 'numeric'],
|
|
||||||
'weight' => ['nullable', 'numeric'],
|
|
||||||
'source' => ['nullable', 'string'],
|
|
||||||
'ingredients.amount' => ['required', 'array', new ArrayNotEmpty],
|
|
||||||
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsDecimalOrFraction],
|
|
||||||
'ingredients.unit' => ['required', 'array'],
|
|
||||||
'ingredients.unit.*' => ['required_with:ingredients.id.*'],
|
|
||||||
'ingredients.detail' => ['required', 'array'],
|
|
||||||
'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.key' => ['nullable', 'array'],
|
|
||||||
'ingredients.key.*' => ['nullable', 'int'],
|
|
||||||
'ingredients.weight' => ['required', 'array', new ArrayNotEmpty],
|
|
||||||
'ingredients.weight.*' => ['required', 'int'],
|
|
||||||
'separators.key' => ['nullable', 'array'],
|
|
||||||
'separators.key.*' => ['nullable', 'int'],
|
|
||||||
'separators.weight' => ['nullable', 'array'],
|
|
||||||
'separators.weight.*' => ['required', 'int'],
|
|
||||||
'separators.text' => ['nullable', 'array'],
|
|
||||||
'separators.text.*' => ['nullable', 'string'],
|
|
||||||
'steps.step' => ['required', 'array', new ArrayNotEmpty],
|
|
||||||
'steps.step.*' => ['nullable', 'string'],
|
|
||||||
'steps.key' => ['nullable', 'array'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Validate that no ingredients are recursive.
|
// Validate that no ingredients are recursive.
|
||||||
// TODO: refactor as custom validator.
|
// TODO: refactor as custom validator.
|
||||||
|
|
@ -285,7 +240,7 @@ class RecipeController extends Controller
|
||||||
->usingFileName("{$recipe->slug}.{$file->extension()}")
|
->usingFileName("{$recipe->slug}.{$file->extension()}")
|
||||||
->toMediaCollection();
|
->toMediaCollection();
|
||||||
}
|
}
|
||||||
elseif (isset($input['remove_image']) && (bool) $input['remove_image']) {
|
elseif (isset($input['remove_image']) && $input['remove_image']) {
|
||||||
$recipe->clearMediaCollection();
|
$recipe->clearMediaCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Rules\ArrayNotEmpty;
|
||||||
|
use App\Rules\StringIsDecimalOrFraction;
|
||||||
|
use App\Rules\UsesIngredientTrait;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateRecipeRequest extends FormRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string'],
|
||||||
|
'description' => ['nullable', 'string'],
|
||||||
|
'description_delta' => ['nullable', 'string'],
|
||||||
|
'image' => ['nullable', 'file', 'mimes:jpg,png,gif'],
|
||||||
|
'remove_image' => ['nullable', 'boolean'],
|
||||||
|
'servings' => ['required', 'numeric'],
|
||||||
|
'time_prep' => ['nullable', 'numeric'],
|
||||||
|
'time_cook' => ['nullable', 'numeric'],
|
||||||
|
'weight' => ['nullable', 'numeric'],
|
||||||
|
'source' => ['nullable', 'string'],
|
||||||
|
'ingredients.amount' => ['required', 'array', new ArrayNotEmpty],
|
||||||
|
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsDecimalOrFraction],
|
||||||
|
'ingredients.unit' => ['required', 'array'],
|
||||||
|
'ingredients.unit.*' => ['required_with:ingredients.id.*'],
|
||||||
|
'ingredients.detail' => ['required', 'array'],
|
||||||
|
'ingredients.detail.*' => ['nullable', 'string'],
|
||||||
|
'ingredients.id' => ['required', 'array', new ArrayNotEmpty],
|
||||||
|
'ingredients.id.*' => ['required_with:ingredients.amount.*', 'required_with:ingredients.unit.*', 'nullable'],
|
||||||
|
'ingredients.type' => ['required', 'array', new ArrayNotEmpty],
|
||||||
|
'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()],
|
||||||
|
'ingredients.key' => ['nullable', 'array'],
|
||||||
|
'ingredients.key.*' => ['nullable', 'int'],
|
||||||
|
'ingredients.weight' => ['required', 'array', new ArrayNotEmpty],
|
||||||
|
'ingredients.weight.*' => ['required', 'int'],
|
||||||
|
'separators.key' => ['nullable', 'array'],
|
||||||
|
'separators.key.*' => ['nullable', 'int'],
|
||||||
|
'separators.weight' => ['nullable', 'array'],
|
||||||
|
'separators.weight.*' => ['required', 'int'],
|
||||||
|
'separators.text' => ['nullable', 'array'],
|
||||||
|
'separators.text.*' => ['nullable', 'string'],
|
||||||
|
'steps.step' => ['required', 'array', new ArrayNotEmpty],
|
||||||
|
'steps.step.*' => ['nullable', 'string'],
|
||||||
|
'steps.key' => ['nullable', 'array'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ingredients.id.*.required_with' => 'Missing :attribute in Ingredients.',
|
||||||
|
'ingredients.amount.*.required_with' => 'Missing :attribute in Ingredients.',
|
||||||
|
'ingredients.unit.*.required_with' => 'Missing :attribute in Ingredients.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ingredients.id.*' => 'ingredient name',
|
||||||
|
'ingredients.amount.*' => 'ingredient amount',
|
||||||
|
'ingredients.unit.*' => 'ingredient unit',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,5 @@
|
||||||
|
@props(['hasError' => false])
|
||||||
|
|
||||||
<div x-data="picker()">
|
<div x-data="picker()">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -11,7 +13,7 @@
|
||||||
x-ref="ingredients_type"/>
|
x-ref="ingredients_type"/>
|
||||||
<x-inputs.input type="text"
|
<x-inputs.input type="text"
|
||||||
name="ingredients[name][]"
|
name="ingredients[name][]"
|
||||||
class="w-full"
|
class="w-full{{ $hasError ? ' border-red-600' : '' }}"
|
||||||
value="{{ $defaultName ?? '' }}"
|
value="{{ $defaultName ?? '' }}"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
|
@php($key = $key ?? null)
|
||||||
|
@error("ingredients.amount.{$key}")
|
||||||
|
@php($amount_error = 'border-red-600')
|
||||||
|
@enderror
|
||||||
|
@error("ingredients.unit.{$key}")
|
||||||
|
@php($unit_error = 'border-red-600')
|
||||||
|
@enderror
|
||||||
|
|
||||||
<div class="ingredient draggable">
|
<div class="ingredient draggable">
|
||||||
<x-inputs.input type="hidden" name="ingredients[key][]" :value="$key ?? null" />
|
<x-inputs.input type="hidden" name="ingredients[key][]" :value="$key" />
|
||||||
<x-inputs.input type="hidden" name="ingredients[weight][]" :value="$weight ?? null" />
|
<x-inputs.input type="hidden" name="ingredients[weight][]" :value="$weight ?? null" />
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0 w-full">
|
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0 w-full">
|
||||||
|
|
@ -11,16 +19,17 @@
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<x-ingredient-picker :default-id="$ingredient_id ?? null"
|
<x-ingredient-picker :default-id="$ingredient_id ?? null"
|
||||||
:default-type="$ingredient_type ?? null"
|
:default-type="$ingredient_type ?? null"
|
||||||
:default-name="$ingredient_name ?? null" />
|
:default-name="$ingredient_name ?? null"
|
||||||
|
:has-error="(isset($amount) || isset($unit)) && empty($ingredient_id)"/>
|
||||||
</div>
|
</div>
|
||||||
<x-inputs.input name="ingredients[amount][]"
|
<x-inputs.input name="ingredients[amount][]"
|
||||||
type="text"
|
type="text"
|
||||||
size="5"
|
size="5"
|
||||||
placeholder="Amount"
|
placeholder="Amount"
|
||||||
class="block"
|
class="block {{ $amount_error ?? null }}"
|
||||||
:value="$amount ?? null" />
|
:value="$amount ?? null" />
|
||||||
<x-inputs.select name="ingredients[unit][]"
|
<x-inputs.select name="ingredients[unit][]"
|
||||||
class="block"
|
class="block {{ $unit_error ?? null }}"
|
||||||
:options="$units_supported ?? []"
|
:options="$units_supported ?? []"
|
||||||
:selectedValue="$unit ?? null">
|
:selectedValue="$unit ?? null">
|
||||||
<option value="" selected>Unit</option>
|
<option value="" selected>Unit</option>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue