mirror of https://github.com/kcal-app/kcal.git
312 lines
11 KiB
PHP
312 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* @noinspection PhpDocSignatureInspection
|
|
*/
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Requests\StoreFromNutrientsJournalEntryRequest;
|
|
use App\Http\Requests\StoreJournalEntryRequest;
|
|
use App\Models\Food;
|
|
use App\Models\Goal;
|
|
use App\Models\JournalDate;
|
|
use App\Models\JournalEntry;
|
|
use App\Models\Recipe;
|
|
use App\Support\ArrayFormat;
|
|
use App\Support\Number;
|
|
use App\Support\Nutrients;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Pluralizer;
|
|
use Illuminate\Support\Str;
|
|
|
|
class JournalEntryController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of the resource.
|
|
*/
|
|
public function index(Request $request): View
|
|
{
|
|
$date = $request->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()
|
|
]));
|
|
}
|
|
}
|