mirror of https://github.com/kcal-app/kcal.git
Add support for fractional data entry
This commit is contained in:
parent
3628ab1f51
commit
81f590515d
|
@ -3,6 +3,8 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Food;
|
||||
use App\Rules\StringIsDecimalOrFraction;
|
||||
use App\Support\Number;
|
||||
use App\Support\Nutrients;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
@ -70,7 +72,7 @@ class FoodController extends Controller
|
|||
'name' => 'required|string',
|
||||
'detail' => 'nullable|string',
|
||||
'brand' => 'nullable|string',
|
||||
'serving_size' => 'required|numeric',
|
||||
'serving_size' => ['required', new StringIsDecimalOrFraction],
|
||||
'serving_unit' => 'nullable|string',
|
||||
'serving_weight' => 'required|numeric',
|
||||
'calories' => 'nullable|numeric',
|
||||
|
@ -80,6 +82,7 @@ class FoodController extends Controller
|
|||
'carbohydrates' => 'nullable|numeric',
|
||||
'protein' => 'nullable|numeric',
|
||||
]);
|
||||
$attributes['serving_size'] = Number::floatFromString($attributes['serving_size']);
|
||||
$food->fill(array_filter($attributes))->save();
|
||||
return redirect(route('foods.show', $food))
|
||||
->with('message', 'Changes saved!');
|
||||
|
|
|
@ -9,6 +9,8 @@ use App\Models\Food;
|
|||
use App\Models\JournalEntry;
|
||||
use App\Models\Recipe;
|
||||
use App\Rules\ArrayNotEmpty;
|
||||
use App\Rules\StringIsDecimalOrFraction;
|
||||
use App\Support\Number;
|
||||
use App\Support\Nutrients;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
@ -82,7 +84,7 @@ class JournalEntryController extends Controller
|
|||
'date' => 'required|date',
|
||||
'meal' => 'required|string',
|
||||
'amounts' => ['required', 'array', new ArrayNotEmpty],
|
||||
'amounts.*' => 'required_with:foods.*,recipes.*|nullable|numeric|min:0',
|
||||
'amounts.*' => ['required_with:foods.*,recipes.*', 'nullable', new StringIsDecimalOrFraction],
|
||||
'units' => ['required', 'array', new ArrayNotEmpty],
|
||||
'units.*' => 'nullable|string',
|
||||
'foods' => 'required|array',
|
||||
|
@ -119,7 +121,7 @@ class JournalEntryController extends Controller
|
|||
$food = $foods->get($id);
|
||||
$nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier(
|
||||
$food,
|
||||
$input['amounts'][$key],
|
||||
Number::floatFromString($input['amounts'][$key]),
|
||||
$input['units'][$key],
|
||||
);
|
||||
foreach ($nutrients as $nutrient => $amount) {
|
||||
|
@ -134,7 +136,7 @@ class JournalEntryController extends Controller
|
|||
foreach ($recipes_selected as $key => $id) {
|
||||
$recipe = $recipes->get($id);
|
||||
foreach ($nutrients as $nutrient => $amount) {
|
||||
$nutrients[$nutrient] += $recipe->{"{$nutrient}PerServing"}() * $input['amounts'][$key];
|
||||
$nutrients[$nutrient] += $recipe->{"{$nutrient}PerServing"}() * Number::floatFromString($input['amounts'][$key]);
|
||||
}
|
||||
$summary[] = "{$input['amounts'][$key]} {$input['units'][$key]} {$recipe->name}";
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ use App\Models\FoodAmount;
|
|||
use App\Models\Recipe;
|
||||
use App\Models\RecipeStep;
|
||||
use App\Rules\ArrayNotEmpty;
|
||||
use App\Rules\StringIsDecimalOrFraction;
|
||||
use App\Support\Number;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -63,7 +65,7 @@ class RecipeController extends Controller
|
|||
'description' => 'nullable|string',
|
||||
'servings' => 'required|numeric',
|
||||
'foods_amount' => ['required', 'array', new ArrayNotEmpty],
|
||||
'foods_amount.*' => 'required_with:foods.*|nullable|numeric|min:0',
|
||||
'foods_amount.*' => ['required_with:foods.*', 'nullable', new StringIsDecimalOrFraction],
|
||||
'foods_unit' => ['required', 'array'],
|
||||
'foods_unit.*' => 'nullable|string',
|
||||
'foods' => ['required', 'array', new ArrayNotEmpty],
|
||||
|
@ -88,7 +90,7 @@ class RecipeController extends Controller
|
|||
$weight = 0;
|
||||
foreach (array_filter($input['foods_amount']) as $key => $amount) {
|
||||
$food_amounts[$key] = new FoodAmount([
|
||||
'amount' => (float) $amount,
|
||||
'amount' => Number::floatFromString($amount),
|
||||
'unit' => $input['foods_unit'][$key],
|
||||
'weight' => $weight++,
|
||||
]);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Support\Number;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class StringIsDecimalOrFraction implements Rule
|
||||
{
|
||||
/**
|
||||
* Determine if the string is a decimal or fraction, excluding zero.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
try {
|
||||
$result = Number::floatFromString($value);
|
||||
return $result != 0;
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
// Allow to pass through, method will return false.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message(): string
|
||||
{
|
||||
return 'The :attribute must be a decimal or fraction.';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Phospr\Fraction;
|
||||
|
||||
class Number
|
||||
{
|
||||
/**
|
||||
* Get a float value from a decimal or fraction string.
|
||||
*
|
||||
* @param string $value
|
||||
* Value in decimal or string format.
|
||||
* @return float
|
||||
* Float representation of the value.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function floatFromString(string $value): float {
|
||||
if ((float) $value == $value) {
|
||||
$result = (float) $value;
|
||||
}
|
||||
else {
|
||||
$result = Fraction::fromString($value)->toFloat();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string faction representation of a float.
|
||||
*
|
||||
* @todo Handle repeating values like 1/3, 2/3, etc.
|
||||
*
|
||||
* @see https://rosettacode.org/wiki/Convert_decimal_number_to_rational#PHP
|
||||
*
|
||||
* @param float $value
|
||||
* Value to convert to string fraction.
|
||||
* @return string
|
||||
* String fraction.
|
||||
*/
|
||||
public static function fractionStringFromFloat(float $value): string {
|
||||
$fraction = (string) Fraction::fromFloat($value);
|
||||
if ($fraction === '33333333/100000000') {
|
||||
$fraction = '1/3';
|
||||
}
|
||||
elseif ($fraction === '66666667/100000000') {
|
||||
$fraction = '2/3';
|
||||
}
|
||||
return $fraction;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,8 @@
|
|||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^8.12",
|
||||
"laravel/tinker": "^2.5"
|
||||
"laravel/tinker": "^2.5",
|
||||
"phospr/fraction": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^2.9",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6798cd035bece9ee986d5e3a59036303",
|
||||
"content-hash": "7054301f9158e201ddff5b37f41a1c5a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
@ -1829,6 +1829,49 @@
|
|||
},
|
||||
"time": "2020-11-07T02:01:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phospr/fraction",
|
||||
"version": "v1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phospr/fraction.git",
|
||||
"reference": "3f195b920bca0ba4eac8575e397af283782c699d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phospr/fraction/zipball/3f195b920bca0ba4eac8575e397af283782c699d",
|
||||
"reference": "3f195b920bca0ba4eac8575e397af283782c699d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.0.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Phospr\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tom Haskins-Vaughan",
|
||||
"email": "tom@tomhv.uk"
|
||||
}
|
||||
],
|
||||
"description": "A composer-installable fractions library",
|
||||
"support": {
|
||||
"issues": "https://github.com/phospr/fraction/issues",
|
||||
"source": "https://github.com/phospr/fraction/tree/master"
|
||||
},
|
||||
"time": "2016-12-21T15:33:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.7.5",
|
||||
|
|
|
@ -72,11 +72,10 @@
|
|||
|
||||
<x-inputs.input id="serving_size"
|
||||
class="block mt-1"
|
||||
type="number"
|
||||
step="any"
|
||||
type="text"
|
||||
name="serving_size"
|
||||
size="10"
|
||||
:value="old('serving_size', $food->serving_size)"/>
|
||||
:value="old('serving_size', \App\Support\Number::fractionStringFromFloat($food->serving_size))"/>
|
||||
</div>
|
||||
|
||||
<!-- Serving unit -->
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
@endif
|
||||
<div class="font-bold">
|
||||
Serving size {{ $food->serving_size }}
|
||||
Serving size {{ \App\Support\Number::fractionStringFromFloat($food->serving_size) }}
|
||||
{{ $food->serving_unit }}
|
||||
({{ $food->serving_weight }}g)
|
||||
</div>
|
||||
|
|
|
@ -60,11 +60,10 @@
|
|||
<x-inputs.label for="recipes" :value="__('Recipe')" class="col-span-4"/>
|
||||
@for ($i = 0; $i < 10; $i++)
|
||||
<div>
|
||||
<x-inputs.input type="number"
|
||||
<x-inputs.input type="text"
|
||||
name="amounts[]"
|
||||
class="block w-full"
|
||||
:value="old('amounts.' . $i)"
|
||||
step="any" />
|
||||
:value="old('amounts.' . $i)" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<x-inputs.select name="units[]"
|
||||
|
|
|
@ -69,10 +69,10 @@
|
|||
<h3 class="pt-2 mb-2 font-extrabold">Ingredients</h3>
|
||||
@for($i = 0; $i < 20; $i++)
|
||||
<div class="flex flex-row space-x-4 mb-4">
|
||||
<x-inputs.input type="number"
|
||||
<x-inputs.input type="text"
|
||||
name="foods_amount[]"
|
||||
:value="old('foods_amount.' . $i)"
|
||||
step="any" />
|
||||
size="5"
|
||||
:value="old('foods_amount.' . $i)" />
|
||||
<x-inputs.select name="foods_unit[]"
|
||||
:options="$food_units"
|
||||
:selectedValue="old('foods_unit.' . $i)">
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<h3 class="mb-2 mt-4 font-bold">Ingredients</h3>
|
||||
@foreach($recipe->foodAmounts as $ia)
|
||||
<div class="flex flex-row space-x-2 mb-2">
|
||||
<div>{{ $ia->amount }}</div>
|
||||
<div>{{ \App\Support\Number::fractionStringFromFloat($ia->amount) }}</div>
|
||||
@if($ia->unit)<div>{{ $ia->unit }}</div>@endif
|
||||
<div>{{ $ia->food->name }}</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue