Add backend support for multiple dates/meals in journal entry

This commit is contained in:
Christopher C. Wells 2021-01-24 15:23:18 -08:00
parent 309ec6810c
commit 35f386d681
4 changed files with 104 additions and 40 deletions

View File

@ -12,6 +12,7 @@ use App\Rules\ArrayNotEmpty;
use App\Rules\InArray; use App\Rules\InArray;
use App\Rules\StringIsDecimalOrFraction; use App\Rules\StringIsDecimalOrFraction;
use App\Rules\UsesIngredientTrait; use App\Rules\UsesIngredientTrait;
use App\Support\ArrayFormat;
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;
@ -99,52 +100,62 @@ class JournalEntryController extends Controller
'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()], 'ingredients.type.*' => ['required_with:ingredients.id.*', 'nullable', new UsesIngredientTrait()],
]); ]);
$summary = []; $ingredients = ArrayFormat::flipTwoDimensionalKeys($input['ingredients']);
$nutrients = array_fill_keys(Nutrients::$all, 0);
/** @var \App\Models\JournalEntry[] $entries */
$entries = [];
// TODO: Improve efficiency? Potential for lots of queries here... // TODO: Improve efficiency? Potential for lots of queries here...
foreach ($input['ingredients']['id'] as $key => $id) { foreach ($ingredients as $ingredient) {
if ($input['ingredients']['type'][$key] == Food::class) { // Prepare entry values.
$food = Food::whereId($id)->first(); /**
$nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier( * @var string $date
$food, * @var string $meal
Number::floatFromString($input['ingredients']['amount'][$key]), * @var float $amount
$input['ingredients']['unit'][$key], * @var string $unit
); * @var int $id
foreach ($nutrients as $nutrient => $amount) { * @var string $type
$nutrients[$nutrient] += $food->{$nutrient} * $nutrient_multiplier; */
extract($ingredient, EXTR_OVERWRITE);
$entry_key = "{$date}{$meal}";
$entries[$entry_key] = $entries[$entry_key] ?? JournalEntry::make([
'date' => $date,
'meal' => $meal,
])->user()->associate(Auth::user());
// Calculate amounts based on ingredient type.
if ($type == Food::class) {
$item = Food::whereId($id)->first();
$nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier($item, Number::floatFromString($amount), $unit,);
foreach (Nutrients::$all as $nutrient) {
$entries[$entry_key]->{$nutrient} =+ $item->{$nutrient} * $nutrient_multiplier;
} }
$summary[] = "{$input['ingredients']['amount'][$key]} {$input['ingredients']['unit'][$key]} {$food->name}"; $entries[$entry_key]->foods->add($item);
} }
elseif ($input['ingredients']['type'][$key] == Recipe::class) { elseif ($type == Recipe::class) {
$recipe = Recipe::whereId($id)->first(); $item = Recipe::whereId($id)->first();
foreach ($nutrients as $nutrient => $amount) { foreach (Nutrients::$all as $nutrient) {
$nutrients[$nutrient] += $recipe->{"{$nutrient}PerServing"}() * Number::floatFromString($input['ingredients']['amount'][$key]); $entries[$entry_key]->{$nutrient} += $item->{"{$nutrient}PerServing"}() * Number::floatFromString($amount);
} }
$summary[] = "{$input['ingredients']['amount'][$key]} {$input['ingredients']['unit'][$key]} {$recipe->name}"; $entries[$entry_key]->recipes->add($item);
} }
else {
return back()->withInput()->withErrors("Invalid ingredient type {$type}.");
}
// Update summary
$entries[$entry_key]->summary .= (!empty($entries[$entry_key]->summary) ? ', ' : null) . "{$amount} {$unit} {$item->name}";
} }
$entry = new JournalEntry([ foreach ($entries as $entry) {
'summary' => implode(', ', $summary), $entry->save();
'date' => $input['date'], $entry->user->save();
'meal' => $input['meal'], $entry->foods()->saveMany($entry->foods);
] + $nutrients); $entry->recipes()->saveMany($entry->recipes);
$entry->user()->associate(Auth::user());
if ($entry->save()) {
if (isset($foods)) {
$entry->foods()->saveMany($foods);
}
if (isset($recipes)) {
$entry->recipes()->saveMany($recipes);
}
} }
session()->flash('message', "Journal entry added!"); $count = count($entries);
return redirect()->route( session()->flash('message', "Added {$count} journal entries!");
'journal-entries.index', return redirect()->route('journal-entries.index');
['date' => $entry->date->format('Y-m-d')]
);
} }
/** /**

View File

@ -0,0 +1,55 @@
<?php
namespace App\Support;
class ArrayFormat
{
/**
* Flip the parent and child keys of a two-dimensional array.
*
* The primary use case for this is to simplify the results of a form using
* input names like `ingredient[]` and `amount[]`.
*
* E.g this array:
*
* $from = [
* 'ingredient' => [
* 0 => 'ingredient-0',
* 1 => 'ingredient-1',
* 2 => 'ingredient-2',
* 3 => 'ingredient-3',
* ],
* 'amount' => [
* 0 => 'amount-0',
* 1 => 'amount-1',
* 2 => 'amount-2',
* 3 => 'amount-3',
* ]
* ]
*
* becomes:
*
* $to = [
* 0 => ['ingredient' => 'ingredient-0', 'amount' => 'amount-0'],
* 1 => ['ingredient' => 'ingredient-1', 'amount' => 'amount-1'],
* 2 => ['ingredient' => 'ingredient-2', 'amount' => 'amount-2'],
* 3 => ['ingredient' => 'ingredient-3', 'amount' => 'amount-3'],
* ]
*
* @param array $array
* Two dimensional array to be "flipped".
*
* @return array
* The flipped array.
*/
public static function flipTwoDimensionalKeys(array $array): array {
$flipped = [];
foreach ($array as $parent_key => $parent_values) {
foreach ($parent_values as $child_key => $child_value) {
$flipped[$child_key][$parent_key] = $child_value;
}
}
return $flipped;
}
}

View File

@ -34,9 +34,7 @@
</x-inputs.icon-button> </x-inputs.icon-button>
</div> </div>
<div class="flex items-center justify-end mt-4"> <div class="flex items-center justify-end mt-4">
<x-inputs.button class="ml-3"> <x-inputs.button class="ml-3">Add entries</x-inputs.button>
{{ __('Add') }}
</x-inputs.button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -37,7 +37,7 @@
{{ __('View Journal') }} {{ __('View Journal') }}
</x-dropdown-link> </x-dropdown-link>
<x-dropdown-link :href="route('journal-entries.create')"> <x-dropdown-link :href="route('journal-entries.create')">
{{ __('Add Entry') }} {{ __('Add Entries') }}
</x-dropdown-link> </x-dropdown-link>
</x-slot> </x-slot>
</x-dropdown> </x-dropdown>