date ?? Carbon::now()->toDateString(); $date = Carbon::rawCreateFromFormat('Y-m-d', $date); // Get entries and nutrient sums for the day. $entries = JournalEntry::where([ 'user_id' => Auth::user()->id, 'date' => $date->toDateString(), ])->get(); $sums = []; foreach (Nutrients::all()->pluck('value') as $nutrient) { $sums[$nutrient] = round($entries->sum($nutrient)); } // Get daily goals data for user. $goal = Auth::user()->getGoalByDate($date); $goalProgress = []; if ($goal) { foreach (Nutrients::all()->pluck('value') as $nutrient) { if ($goal->{$nutrient} > 0) { $goalProgress[$nutrient] = round($sums[$nutrient] / $goal->{$nutrient} * 100); $goalProgress[$nutrient] .= '%'; } } } // Get all goals as options to change for the date. $goalOptions = Goal::whereUserId(Auth::user()->id) ->orderBy('name') ->get() ->map(function (Goal $goal) { return ['value' => $goal->id, 'label' => $goal->name]; }); // Get the associated journal date. // @todo Refactor journal date as a relationship on journal entries. $journalDate = JournalDate::getOrCreateJournalDate(Auth::user(), $date); return view('journal-entries.index') ->with('entries', $entries) ->with('sums', $sums) ->with('currentGoal', $goal) ->with('goalProgress', $goalProgress) ->with('goalOptions', $goalOptions) ->with('journalDate', $journalDate) ->with('date', $date); } /** * Show the form for creating a new resource. */ public function create(Request $request): View { $date = $request->date ?? Carbon::now()->toDateString(); $ingredients = []; if ($old = old('ingredients')) { foreach ($old['amount'] as $key => $amount) { if ( empty($old['date'][$key]) && empty($old['meal'][$key]) && empty($amount) && empty($old['unit'][$key]) && empty($old['id'][$key]) ) { continue; } $ingredients[$key] = [ 'key' => $key, 'date' => $old['date'][$key], 'meal' => $old['meal'][$key], 'amount' => $amount, 'unit' => $old['unit'][$key], 'id' => $old['id'][$key], 'type' => $old['type'][$key], 'name' => $old['name'][$key], ]; // Add supported units for the ingredient. $ingredient = NULL; if ($ingredients[$key]['type'] === Food::class) { $ingredient = Food::whereId($ingredients[$key]['id'])->first(); } elseif ($ingredients[$key]['type'] === Recipe::class) { $ingredient = Recipe::whereId($ingredients[$key]['id'])->first(); } if ($ingredient) { $ingredients[$key]['units'] = $ingredient->units_supported; } } } return view('journal-entries.create') ->with('ingredients', $ingredients) ->with('units', Nutrients::units()->toArray()) ->with('default_date', Carbon::createFromFormat('Y-m-d', $date)); } /** * Show the form for creating a journal entry from nutrients directly. */ public function createFromNutrients(Request $request): View { $date = $request->date ?? Carbon::now()->toDateString(); return view('journal-entries.create-from-nutrients') ->with('units', Nutrients::units()->toArray()) ->with('default_date', Carbon::createFromFormat('Y-m-d', $date)); } /** * Store a newly created resource in storage. */ public function store(StoreJournalEntryRequest $request): RedirectResponse { $input = $request->validated(); $ingredients = ArrayFormat::flipTwoDimensionalKeys($input['ingredients']); /** @var \App\Models\JournalEntry[] $entries */ $entries = []; $entry_key = 0; $group_entries = isset($input['group_entries']) && (bool) $input['group_entries']; // TODO: Improve efficiency. Potential for lots of queries here... foreach ($ingredients as $ingredient) { // Set entry key (combined date and meal or individual entries). if ($group_entries) { $entry_key = "{$ingredient['date']}{$ingredient['meal']}"; } else { $entry_key++; } // Get an existing entry (when grouping) or create a new one. $entries[$entry_key] = $entries[$entry_key] ?? JournalEntry::make([ 'date' => $ingredient['date'], 'meal' => $ingredient['meal'], ])->user()->associate(Auth::user()); $entry = &$entries[$entry_key]; // Calculate amounts based on ingredient type. $item = NULL; $amount = Number::floatFromString($ingredient['amount']); if ($ingredient['type'] == Food::class) { $item = Food::whereId($ingredient['id'])->first(); $nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier($item, $amount, $ingredient['unit']); foreach (Nutrients::all()->pluck('value') as $nutrient) { $entry->{$nutrient} += $item->{$nutrient} * $nutrient_multiplier; } $entry->foods->add($item); } elseif ($ingredient['type'] == Recipe::class) { $item = Recipe::whereId($ingredient['id'])->first(); foreach (Nutrients::all()->pluck('value') as $nutrient) { $entry->{$nutrient} += Nutrients::calculateRecipeNutrientAmount($item, $nutrient, $amount, $ingredient['unit']); } $entry->recipes->add($item); } // Add to summary. if (!empty($entry->summary)) { $entry->summary .= '; '; } $entry->summary .= $this->createIngredientSummary($ingredient, $item, $amount); } // Save all new entries. foreach ($entries as $new_entry) { $new_entry->save(); $new_entry->user->save(); $new_entry->foods()->saveMany($new_entry->foods); $new_entry->recipes()->saveMany($new_entry->recipes); } $count = count($entries); session()->flash('message', "Added {$count} journal entries!"); // Redirect to the date if only one date is used. $parameters = []; $unique_dates = array_unique($input['ingredients']['date']); if (count($unique_dates) === 1) { $parameters['date'] = reset($unique_dates); } return redirect()->route('journal-entries.index', $parameters); } /** * Attempt to create a coherent summary for an entry ingredient. */ private function createIngredientSummary(array $ingredient, Food|Recipe $item, float $amount): string { $name = $item->name; $unit = $ingredient['unit']; // Determine unit with special handling for custom Food units. if ($item instanceof Food) { if ($unit === 'serving') { $no_serving_unit = empty($item->serving_unit) && empty($item->serving_unit_name); // If there is no serving unit or the serving unit name is // exactly the same as the item name don't use a serving // unit and pluralize the _item_ name. if ($no_serving_unit || $item->serving_unit_name === $name) { $unit = null; $name = Pluralizer::plural($name, $amount); } // If the serving unit name is already _part_ of the item // name, just keep the defined unit (e.g. name: "tortilla // chips" and serving name "chips"). elseif (Str::contains($name, $item->serving_unit_name)) { $unit = 'serving'; } // If a serving unit name is set, use the formatted serving // unit name as a base. elseif (!empty($item->serving_unit_name)) { $unit = $item->serving_unit_formatted; } } } // Pluralize unit with supplied plurals or Pluralizer. if (Nutrients::units()->has($unit)) { $value = 'label'; if ($amount > 1) { $value = 'plural'; } $unit = Nutrients::units()->get($unit)[$value]; } else { $unit = Pluralizer::plural($unit, $amount); } // Add amount, unit, and name to summary. $amount = Number::rationalStringFromFloat($amount); $summary = "{$amount} {$unit} {$name}"; // Add detail if available. if (isset($item->detail) && !empty($item->detail)) { $summary .= ", {$item->detail}"; } return $summary; } /** * Store an entry from nutrients. */ public function storeFromNutrients(StoreFromNutrientsJournalEntryRequest $request): RedirectResponse { $attributes = $request->validated(); $entry = JournalEntry::make(array_filter($attributes, function ($value) { return !is_null($value); }))->user()->associate(Auth::user()); $entry->save(); session()->flash('message', "Journal entry added!"); return redirect()->route( 'journal-entries.index', ['date' => $entry->date->format('Y-m-d')] ); } /** * Confirm removal of the specified resource. */ public function delete(JournalEntry $journal_entry): View { return view('journal-entries.delete') ->with('journal_entry', $journal_entry); } /** * Remove the specified resource from storage. */ public function destroy(JournalEntry $journal_entry): RedirectResponse { $journal_entry->delete(); session()->flash('message', 'Journal entry deleted!'); return redirect(route('journal-entries.index', [ 'date' => $journal_entry->date->toDateString() ])); } }