Improve Jorunal Entry validation feedback

This commit is contained in:
Christopher C. Wells 2021-04-17 19:39:07 -07:00
parent 0982ac1601
commit ff5661fdf1
11 changed files with 150 additions and 63 deletions

View File

@ -5,13 +5,11 @@
namespace App\Http\Controllers;
use App\Http\Requests\StoreFromNutrientsJournalEntryRequest;
use App\Http\Requests\StoreJournalEntryRequest;
use App\Models\Food;
use App\Models\JournalEntry;
use App\Models\Recipe;
use App\Rules\ArrayNotEmpty;
use App\Rules\InArray;
use App\Rules\StringIsDecimalOrFraction;
use App\Rules\UsesIngredientTrait;
use App\Support\ArrayFormat;
use App\Support\Number;
use App\Support\Nutrients;
@ -85,6 +83,7 @@ class JournalEntryController extends Controller
continue;
}
$ingredients[$key] = [
'key' => $key,
'date' => $old['date'][$key],
'meal' => $old['meal'][$key],
'amount' => $amount,
@ -130,28 +129,9 @@ class JournalEntryController extends Controller
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): RedirectResponse
public function store(StoreJournalEntryRequest $request): RedirectResponse
{
$input = $request->validate([
'ingredients.date' => ['required', 'array', new ArrayNotEmpty],
'ingredients.date.*' => ['nullable', 'date', 'required_with:ingredients.id.*'],
'ingredients.meal' => ['required', 'array', new ArrayNotEmpty],
'ingredients.meal.*' => [
'nullable',
'string',
'required_with:ingredients.id.*',
new InArray(JournalEntry::meals()->pluck('value')->toArray())
],
'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.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()],
'group_entries' => ['nullable', 'boolean'],
]);
$input = $request->validated();
$ingredients = ArrayFormat::flipTwoDimensionalKeys($input['ingredients']);
@ -284,23 +264,8 @@ class JournalEntryController extends Controller
/**
* Store an entry from nutrients.
*/
public function storeFromNutrients(Request $request): RedirectResponse {
$attributes = $request->validate([
'date' => ['required', 'date'],
'meal' => [
'required',
'string',
new InArray(JournalEntry::meals()->pluck('value')->toArray())
],
'summary' => ['required', 'string'],
'calories' => ['nullable', 'required_without_all:fat,cholesterol,sodium,carbohydrates,protein', 'numeric'],
'fat' => ['nullable', 'required_without_all:calories,cholesterol,sodium,carbohydrates,protein', 'numeric'],
'cholesterol' => ['nullable', 'required_without_all:calories,fat,sodium,carbohydrates,protein', 'numeric'],
'sodium' => ['nullable', 'required_without_all:calories,fat,cholesterol,carbohydrates,protein', 'numeric'],
'carbohydrates' => ['nullable', 'required_without_all:calories,fat,cholesterol,sodium,protein', 'numeric'],
'protein' => ['nullable', 'required_without_all:calories,fat,cholesterol,sodium,carbohydrates', 'numeric'],
]);
public function storeFromNutrients(StoreFromNutrientsJournalEntryRequest $request): RedirectResponse {
$attributes = $request->validated();
$entry = JournalEntry::make(array_filter($attributes))
->user()->associate(Auth::user());
$entry->save();

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests;
use App\Models\JournalEntry;
use App\Rules\ArrayNotEmpty;
use App\Rules\InArray;
use App\Rules\StringIsPositiveDecimalOrFraction;
use App\Rules\UsesIngredientTrait;
use Illuminate\Foundation\Http\FormRequest;
class StoreFromNutrientsJournalEntryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'date' => ['required', 'date'],
'meal' => [
'required',
'string',
new InArray(JournalEntry::meals()->pluck('value')->toArray())
],
'summary' => ['required', 'string'],
'calories' => ['nullable', 'numeric', 'min:0', 'required_without_all:fat,cholesterol,sodium,carbohydrates,protein'],
'fat' => ['nullable', 'numeric', 'min:0', 'required_without_all:calories,cholesterol,sodium,carbohydrates,protein'],
'cholesterol' => ['nullable', 'numeric', 'min:0', 'required_without_all:calories,fat,sodium,carbohydrates,protein'],
'sodium' => ['nullable', 'numeric', 'min:0', 'required_without_all:calories,fat,cholesterol,carbohydrates,protein'],
'carbohydrates' => ['nullable', 'numeric', 'min:0', 'required_without_all:calories,fat,cholesterol,sodium,protein'],
'protein' => ['nullable', 'numeric', 'min:0', 'required_without_all:calories,fat,cholesterol,sodium,carbohydrates'],
];
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests;
use App\Models\JournalEntry;
use App\Rules\ArrayNotEmpty;
use App\Rules\InArray;
use App\Rules\StringIsPositiveDecimalOrFraction;
use App\Rules\UsesIngredientTrait;
use Illuminate\Foundation\Http\FormRequest;
class StoreJournalEntryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'ingredients.date' => ['required', 'array', new ArrayNotEmpty],
'ingredients.date.*' => ['nullable', 'date', 'required_with:ingredients.id.*'],
'ingredients.meal' => ['required', 'array', new ArrayNotEmpty],
'ingredients.meal.*' => [
'nullable',
'string',
'required_with:ingredients.id.*',
new InArray(JournalEntry::meals()->pluck('value')->toArray())
],
'ingredients.amount' => ['required', 'array', new ArrayNotEmpty],
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsPositiveDecimalOrFraction],
'ingredients.unit' => ['required', 'array'],
'ingredients.unit.*' => ['required_with:ingredients.id.*'],
'ingredients.id.*' => 'required_with:ingredients.amount.*|nullable',
'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()],
'group_entries' => ['nullable', 'boolean'],
];
}
/**
* @inheritdoc
*/
public function attributes(): array
{
return [
'ingredients.amount' => 'amount',
'ingredients.amount.*' => 'amount',
'ingredients.date' => 'date',
'ingredients.date.*' => 'date',
'ingredients.id.*' => 'item',
'ingredients.meal' => 'meal',
'ingredients.meal.*' => 'meal',
'ingredients.unit' => 'unit',
'ingredients.unit.*' => 'unit',
];
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Requests;
use App\Rules\StringIsDecimalOrFraction;
use App\Rules\StringIsPositiveDecimalOrFraction;
use Illuminate\Foundation\Http\FormRequest;
class UpdateFoodRequest extends FormRequest
@ -19,7 +19,7 @@ class UpdateFoodRequest extends FormRequest
'brand' => ['nullable', 'string'],
'source' => ['nullable', 'string'],
'notes' => ['nullable', 'string'],
'serving_size' => ['required', new StringIsDecimalOrFraction],
'serving_size' => ['required', new StringIsPositiveDecimalOrFraction],
'serving_unit' => ['nullable', 'string'],
'serving_unit_name' => ['nullable', 'string'],
'serving_weight' => ['required', 'numeric', 'min:0'],

View File

@ -3,7 +3,7 @@
namespace App\Http\Requests;
use App\Rules\ArrayNotEmpty;
use App\Rules\StringIsDecimalOrFraction;
use App\Rules\StringIsPositiveDecimalOrFraction;
use App\Rules\UsesIngredientTrait;
use Illuminate\Foundation\Http\FormRequest;
@ -27,7 +27,7 @@ class UpdateRecipeRequest extends FormRequest
'weight' => ['nullable', 'numeric'],
'source' => ['nullable', 'string'],
'ingredients.amount' => ['required', 'array', new ArrayNotEmpty],
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsDecimalOrFraction],
'ingredients.amount.*' => ['required_with:ingredients.id.*', 'nullable', new StringIsPositiveDecimalOrFraction],
'ingredients.unit' => ['required', 'array'],
'ingredients.unit.*' => ['required_with:ingredients.id.*'],
'ingredients.detail' => ['required', 'array'],

View File

@ -5,7 +5,7 @@ namespace App\Rules;
use App\Support\Number;
use Illuminate\Contracts\Validation\Rule;
class StringIsDecimalOrFraction implements Rule
class StringIsPositiveDecimalOrFraction implements Rule
{
/**
* {@inheritdoc}
@ -14,7 +14,7 @@ class StringIsDecimalOrFraction implements Rule
{
try {
$result = Number::floatFromString($value);
return $result != 0;
return $result > 0;
}
catch (\InvalidArgumentException $e) {
// Allow to pass through, method will return false.
@ -27,6 +27,6 @@ class StringIsDecimalOrFraction implements Rule
*/
public function message(): string
{
return 'The :attribute must be a decimal or fraction.';
return 'The :attribute must be a positive decimal or fraction.';
}
}

View File

@ -8,20 +8,20 @@ use Illuminate\View\Component;
class Select extends Component
{
public ?bool $hasError;
public Collection|array $options;
public ?string $selectedValue;
/**
* Select constructor.
*
* @param \Illuminate\Support\Collection|array $options
* @param ?string $selectedValue
*/
public function __construct(
Collection|array $options,
?bool $hasError = false,
?string $selectedValue = '',
) {
$this->options = $options;
$this->hasError = $hasError;
$this->selectedValue = $selectedValue;
}
@ -29,6 +29,7 @@ class Select extends Component
{
return view('components.inputs.select')
->with('options', $this->options)
->with('hasError', $this->hasError)
->with('selectedValue', $this->selectedValue);
}

View File

@ -1,4 +1,23 @@
<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']) }}>
@props(['disabled' => false, 'hasError' => false])
@php
$classes = [
'rounded-md',
'shadow-sm',
'border-gray-300',
'focus:border-indigo-300',
'focus:ring',
'focus:ring-indigo-200',
'focus:ring-opacity-50',
];
if ($hasError) {
$classes[] = 'border-red-600';
}
@endphp
<select
{{ $disabled ? 'disabled' : '' }}
{!! $attributes->merge(['class' => implode(' ', $classes)]) !!}>
{{ $slot }}
<x-inputs.select-options :options="$options" :selectedValue="$selectedValue" />
</select>

View File

@ -19,6 +19,7 @@
type="date"
class="block w-full"
:value="old('date', $default_date->toDateString())"
:hasError="$errors->has('date')"
required />
</div>
@ -30,6 +31,7 @@
class="block w-full"
:options="$meals"
:selectedValue="old('meal')"
:hasError="$errors->has('meal')"
required>
<option value=""></option>
</x-inputs.select>
@ -43,6 +45,7 @@
type="text"
class="block w-full"
:value="old('summary')"
:hasError="$errors->has('summary')"
required />
</div>
</div>
@ -58,7 +61,8 @@
type="number"
step="any"
class="block w-full"
:value="old($nutrient['value'])"/>
:value="old($nutrient['value'])"
:hasError="$errors->has($nutrient['value'])"/>
</div>
@endforeach
</div>

View File

@ -1,3 +1,4 @@
@php($key = $key ?? null)
<div x-data class="entry-item flex items-center space-x-2">
<div class="flex flex-col space-y-4 w-full">
<!-- Ingredient -->
@ -15,6 +16,7 @@
type="date"
class="block w-full"
:value="$date ?? $default_date->toDateString()"
:hasError="$errors->has('ingredients.date.' . $key)"
required />
</div>
@ -25,8 +27,9 @@
class="block w-full"
:options="$meals"
:selectedValue="$meal ?? null"
:hasError="$errors->has('ingredients.meal.' . $key)"
required>
<option value="">-- Meal --</option>
@if(is_null($key))<option value="">-- Meal --</option>@endif
</x-inputs.select>
</div>
@ -39,6 +42,7 @@
class="block w-full"
placeholder="Amount"
:value="$amount ?? null"
:hasError="$errors->has('ingredients.amount.' . $key)"
required />
</div>
@ -48,8 +52,9 @@
<x-inputs.select name="ingredients[unit][]"
class="block w-full"
:options="$units ?? []"
:selectedValue="$unit ?? null">
<option value="">-- Unit --</option>
:selectedValue="$unit ?? null"
:hasError="$errors->has('ingredients.unit.' . $key)">
@if(is_null($key))<option value="">-- Unit --</option>@endif
</x-inputs.select>
</div>
</div>

View File

@ -2,9 +2,9 @@
namespace Tests\Unit\Rules;
use App\Rules\StringIsDecimalOrFraction;
use App\Rules\StringIsPositiveDecimalOrFraction;
class StringIsDecimalOrFractionTest extends RulesTestCase
class StringIsPositiveDecimalOrFractionTest extends RulesTestCase
{
/**
@ -13,7 +13,7 @@ class StringIsDecimalOrFractionTest extends RulesTestCase
public function setUp(): void
{
parent::setUp();
$this->validator->setRules([new StringIsDecimalOrFraction()]);
$this->validator->setRules([new StringIsPositiveDecimalOrFraction()]);
}
/**
@ -49,16 +49,16 @@ class StringIsDecimalOrFractionTest extends RulesTestCase
/**
* Provide valid decimals or fractions
*
* @see \Tests\Unit\Rules\StringIsDecimalOrFractionTest::testStringIsDecimalOrFractionRule()
* @see \Tests\Unit\Rules\StringIsPositiveDecimalOrFractionTest::testStringIsDecimalOrFractionRule()
*/
public function invalidDecimalsAndFractions(): array {
return [['0'], [0], ['string']];
return [['-1'], [-1], ['0'], [0], ['string']];
}
/**
* Provide valid decimals or fractions
*
* @see \Tests\Unit\Rules\StringIsDecimalOrFractionTest::testStringIsDecimalOrFractionRule()
* @see \Tests\Unit\Rules\StringIsPositiveDecimalOrFractionTest::testStringIsDecimalOrFractionRule()
*/
public function validDecimalsAndFractions(): array {
return [