Add goals support (#4)

This commit is contained in:
Christopher Charbonneau Wells 2021-02-15 14:19:53 -08:00 committed by GitHub
parent 33a8591c72
commit 414629b469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 615 additions and 40 deletions

View File

@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers;
use App\Models\Goal;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
class GoalController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): View
{
if ($request->date) {
$date = Carbon::createFromFormat('Y-m-d', $request->date);
}
else {
$date = Carbon::now();
}
return view('goals.index')
->with('date', $date)
->with('goals', Auth::user()->getGoalsByTime($date));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function create(): View
{
return $this->edit(new Goal());
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): RedirectResponse
{
return $this->update($request, new Goal());
}
/**
* Display the specified resource.
*/
public function show(Goal $goal): View
{
return view('goals.show')
->with('goal', $goal)
->with('nameOptions', Goal::getNameOptions())
->with('frequencyOptions', Goal::$frequencyOptions);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Goal $goal): View
{
return view('goals.edit')
->with('goal', $goal)
->with('nameOptions', Goal::getNameOptions())
->with('frequencyOptions', Goal::$frequencyOptions);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Goal $goal): RedirectResponse
{
$attributes = $request->validate([
'from' => ['nullable', 'date'],
'to' => ['nullable', 'date'],
'frequency' => ['nullable', 'string'],
'name' => ['required', 'string'],
'goal' => ['required', 'numeric'],
]);
$goal->fill($attributes)->user()->associate(Auth::user());
$goal->save();
session()->flash('message', "Goal updated!");
return redirect()->route('goals.show', $goal);
}
/**
* Confirm removal of specified resource.
*/
public function delete(Goal $goal): View
{
return view('goals.delete')->with('goal', $goal);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Goal $goal): RedirectResponse
{
$goal->delete();
return redirect(route('goals.index'))
->with('message', "Goal deleted!");
}
}

View File

@ -29,12 +29,39 @@ class JournalEntryController extends Controller
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 as $nutrient) {
$sums[$nutrient['value']] = round($entries->sum($nutrient['value']));
}
// Get daily goals data for user.
$goals = Auth::user()->getGoalsByTime($date);
$dailyGoals = [];
foreach (Nutrients::$all as $nutrient) {
$goal = $goals['present']
->where('frequency', 'daily')
->where('name', $nutrient['value'])
->first();
if ($goal) {
$dailyGoals[$goal->name] = round($sums[$goal->name] / $goal->goal * 100);
if ($dailyGoals[$goal->name] > 0) {
$dailyGoals[$goal->name] .= '%';
}
}
}
return view('journal-entries.index')
->with('entries', JournalEntry::where([
'user_id' => Auth::user()->id,
'date' => $date,
])->get())
->with('date', Carbon::createFromFormat('Y-m-d', $date));
->with('entries', $entries)
->with('sums', $sums)
->with('dailyGoals', $dailyGoals)
->with('date', $date);
}
/**

102
app/Models/Goal.php Normal file
View File

@ -0,0 +1,102 @@
<?php
namespace App\Models;
use App\Support\Nutrients;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\Goal
*
* @property int $id
* @property int $user_id
* @property \datetime|null $from
* @property \datetime|null $to
* @property string|null $frequency
* @property string $name
* @property float $goal
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read string $summary
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder|Goal newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Goal newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Goal query()
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereFrequency($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereFrom($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereGoal($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereTo($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Goal whereUserId($value)
* @mixin \Eloquent
*/
final class Goal extends Model
{
use HasFactory;
/**
* Supported options for thr frequency attribute.
*/
public static array $frequencyOptions = [
'daily' => ['value' => 'daily', 'label' => 'daily'],
];
/**
* @inheritdoc
*/
protected $fillable = [
'frequency',
'from',
'goal',
'name',
'to',
];
/**
* @inheritdoc
*/
protected $casts = [
'from' => 'datetime:Y-m-d',
'goal' => 'float',
'to' => 'datetime:Y-m-d',
];
/**
* @inheritdoc
*/
protected $appends = [
'summary',
];
/**
* Get the User this goal belongs to.
*/
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
public function getSummaryAttribute(): string {
$nameOptions = self::getNameOptions();
return number_format($this->goal) . "{$nameOptions[$this->name]['unit']} {$nameOptions[$this->name]['label']} {$this->frequency}";
}
/**
* Get options for the "name" column.
*/
public static function getNameOptions(): array {
$options = [];
foreach (Nutrients::$all as $nutrient) {
$options[$nutrient['value']] = [
'value' => $nutrient['value'],
'label' => $nutrient['label'],
'unit' => $nutrient['unit'],
];
}
return $options;
}
}

View File

@ -69,7 +69,7 @@ final class JournalEntry extends Model
];
/**
* The attributes that should be cast.
* @inheritdoc
*/
protected $casts = [
'calories' => 'float',

View File

@ -2,10 +2,13 @@
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
/**
* App\Models\User
@ -32,6 +35,8 @@ use Illuminate\Notifications\Notifiable;
* @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
* @mixin \Eloquent
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Goal[] $goals
* @property-read int|null $goals_count
*/
final class User extends Authenticatable
{
@ -60,4 +65,38 @@ final class User extends Authenticatable
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the User's goals.
*/
public function goals(): HasMany {
return $this->hasMany(Goal::class);
}
/**
* Get User's past, present, and future goals.
*
* @return \App\Models\Goal[]
*/
public function getGoalsByTime(?Carbon $date = null): array {
$now = $date ?? Carbon::now();
$goals = ['past' => new Collection(), 'present' => new Collection(), 'future' => new Collection()];
Goal::all()->where('user_id', Auth::user()->id)
->each(function ($item) use(&$goals, $now) {
if ($item->to && $now->isAfter($item->to)) {
$goals['past'][$item->id] = $item;
}
elseif ($item->from && $now->isBefore($item->from)) {
$goals['future'][$item->id] = $item;
}
elseif (
empty($item->from)
|| empty($item->to)
|| $now->isBetween($item->from, $item->to)
) {
$goals['present'][$item->id] = $item;
}
});
return $goals;
}
}

View File

@ -10,21 +10,21 @@ class Nutrients
public static float $gramsPerOunce = 28.349523125;
public static array $all = [
['value' => 'calories', 'unit' => null],
['value' => 'fat', 'unit' => 'g'],
['value' => 'cholesterol', 'unit' => 'mg'],
['value' => 'sodium', 'unit' => 'mg'],
['value' => 'carbohydrates', 'unit' => 'g'],
['value' => 'protein', 'unit' => 'g'],
'calories' => ['value' => 'calories', 'label' => 'calories', 'unit' => null],
'carbohydrates' => ['value' => 'carbohydrates', 'label' => 'carbohydrates', 'unit' => 'g'],
'cholesterol' => ['value' => 'cholesterol', 'label' => 'cholesterol', 'unit' => 'mg'],
'fat' => ['value' => 'fat', 'label' => 'fat', 'unit' => 'g'],
'protein' => ['value' => 'protein', 'label' => 'protein', 'unit' => 'g'],
'sodium' => ['value' => 'sodium', 'label' => 'sodium', 'unit' => 'mg'],
];
public static array $units = [
['value' => 'tsp', 'label' => 'tsp.'],
['value' => 'tbsp', 'label' => 'tbsp.'],
['value' => 'cup', 'label' => 'cup'],
['value' => 'oz', 'label' => 'oz'],
['value' => 'gram', 'label' => 'grams'],
['value' => 'serving', 'label' => 'servings'],
'cup' => ['value' => 'cup', 'label' => 'cup'],
'gram' => ['value' => 'gram', 'label' => 'grams'],
'oz' => ['value' => 'oz', 'label' => 'oz'],
'serving' => ['value' => 'serving', 'label' => 'servings'],
'tbsp' => ['value' => 'tbsp', 'label' => 'tbsp.'],
'tsp' => ['value' => 'tsp', 'label' => 'tsp.'],
];
/**

View File

@ -0,0 +1,39 @@
<?php
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateGoalsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('goals', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
$table->date('from')->nullable();
$table->date('to')->nullable();
$table->string('frequency')->nullable();
$table->string('name');
$table->unsignedFloat('goal');
$table->timestamps();
$table->index('user_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goals');
}
}

View File

@ -0,0 +1,28 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Delete {{ $goal->goal }} goal?
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ route('goals.destroy', $goal) }}">
@method('delete')
@csrf
<div class="text-lg pb-3">
Are you sure what to delete your <span class="font-extrabold">{{ $goal->summary }}</span> goal?
</div>
<x-inputs.button class="bg-red-800 hover:bg-red-700">
Yes, delete
</x-inputs.button>
<a class="ml-3 text-gray-500 hover:text-gray-700 hover:border-gray-300"
href="{{ route('goals.show', $goal) }}">No, do not delete</a>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,79 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ ($goal->exists ? 'Edit' : 'Add') }} Goal
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="POST" action="{{ ($goal->exists ? route('goals.update', $goal) : route('goals.store')) }}">
@if ($goal->exists)@method('put')@endif
@csrf
<div class="flex flex-col space-y-4">
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
<!-- From -->
<div class="flex-auto">
<x-inputs.label for="from" value="From"/>
<x-inputs.input name="from"
type="date"
class="block w-full"
:value="old('from', $goal->from?->toDateString())" />
</div>
<!-- To -->
<div class="flex-auto">
<x-inputs.label for="to" value="To"/>
<x-inputs.input name="to"
type="date"
class="block w-full"
:value="old('to', $goal->to?->toDateString())" />
</div>
<!-- Frequency -->
<div class="flex-auto">
<x-inputs.label for="frequency" value="Frequency" />
<x-inputs.select name="frequency"
class="block w-full"
:options="$frequencyOptions"
:selectedValue="old('frequency', $goal->frequency)">
</x-inputs.select>
</div>
<!-- Name -->
<div class="flex-auto">
<x-inputs.label for="name" value="Trackable" />
<x-inputs.select name="name"
class="block w-full"
:options="$nameOptions"
:selectedValue="old('name', $goal->name)"
required>
</x-inputs.select>
</div>
<!-- Goal -->
<div class="flex-auto">
<x-inputs.label for="goal" value="Goal" />
<x-inputs.input name="goal"
type="number"
step="any"
class="block w-full"
:value="old('goal', $goal->goal)"
required />
</div>
</div>
</div>
<div class="flex items-center justify-end mt-4">
<x-inputs.button class="ml-3">
{{ ($goal->exists ? 'Save' : 'Add') }}
</x-inputs.button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,65 @@
<x-app-layout>
<x-slot name="header">
<div class="flex justify-between items-center">
<h2 class="leading-tight text-center">
<div class="text-2xl font-semibold text-gray-800">{{ Auth::user()->name }}'s Goals</div>
<div class="flex items-center space-x-2">
<div>
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300"
href="{{ route(Route::current()->getName(), ['date' => $date->copy()->subDay(1)->toDateString()]) }}">
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.707-10.293a1 1 0 00-1.414-1.414l-3 3a1 1 0 000 1.414l3 3a1 1 0 001.414-1.414L9.414 11H13a1 1 0 100-2H9.414l1.293-1.293z" clip-rule="evenodd" />
</svg>
</a>
</div>
<div class="text-base text-gray-500">{{ $date->format('D, j M Y') }}</div>
<div>
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300"
href="{{ route(Route::current()->getName(), ['date' => $date->copy()->addDay(1)->toDateString()]) }}">
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 1.414L10.586 9H7a1 1 0 100 2h3.586l-1.293 1.293a1 1 0 101.414 1.414l3-3a1 1 0 000-1.414z" clip-rule="evenodd" />
</svg>
</a>
</div>
</div>
</h2>
<a href="{{ route('goals.create') }}" class="inline-flex items-center rounded-md font-semibold text-white p-2 bg-green-500 tracking-widest hover:bg-green-700 active:bg-green-900 focus:outline-none focus:border-green-900 focus:ring ring-green-600 disabled:opacity-25 transition ease-in-out duration-150">
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Add Goal
</a>
</div>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="space-y-4">
@forelse($goals['present'] as $goal)
<div class="flex space-x-2 items-center">
<a class="text-gray-500 hover:text-gray-700 hover:border-gray-300 text-sm"
href="{{ route('goals.edit', $goal) }}">
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
</svg>
</a>
<a class="text-red-500 hover:text-red-700 hover:border-red-300 text-sm"
href="{{ route('goals.delete', $goal) }}">
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</a>
<div class="text-lg font-bold">{{ $goal->summary }}</div>
</div>
@empty
<div>No goals set.</div>
@endforelse
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,40 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight flex flex-auto">
{{ $goal->summary }}
<a class="ml-2 text-gray-500 hover:text-gray-700 hover:border-gray-300 text-sm"
href="{{ route('goals.edit', $goal) }}">
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
</svg>
</a>
<a class="h-6 w-6 text-red-500 hover:text-red-700 hover:border-red-300 float-right text-sm"
href="{{ route('goals.delete', $goal) }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</a>
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="grid grid-cols-2 gap-y-1 gap-x-3 max-w-md inline-grid">
<div class="font-bold">From</div>
<div>{{ $goal->from?->toDateString() ?? 'Any' }}</div>
<div class="font-bold">To</div>
<div>{{ $goal->to?->toDateString() ?? 'Any' }}</div>
<div class="font-bold">Frequency</div>
<div>{{ \Illuminate\Support\Str::ucfirst($frequencyOptions[$goal->frequency]['label']) }}</div>
<div class="font-bold">Trackable</div>
<div>{{ \Illuminate\Support\Str::ucfirst($nameOptions[$goal->name]['label']) }}</div>
<div class="font-bold">Goal</div>
<div>{{ $goal->goal }}{{ $nameOptions[$goal->name]['unit'] }}</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -37,22 +37,65 @@
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex align-top flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<div class="w-full sm:w-2/5 md:w-1/3 lg:w-1/4">
<h3 class="font-semibold text-xl text-gray-800">{{ $date->format('D, j M Y') }}</h3>
<div class="text-gray-700">{{ $entries->count() }} {{ \Illuminate\Support\Pluralizer::plural('entry', $entries->count()) }}</div>
<div class="grid grid-cols-2 text-sm border-t-8 border-black pt-2">
<div class="font-extrabold text-lg border-b-4 border-black">Calories</div>
<div class="font-extrabold text-right text-lg border-b-4 border-black">{{ round($entries->sum('calories'), 2) }}</div>
<div class="font-bold border-b border-gray-300">Fat</div>
<div class="text-right border-b border-gray-300">{{ round($entries->sum('fat'), 2) }}g</div>
<div class="font-bold border-b border-gray-300">Cholesterol</div>
<div class="text-right border-b border-gray-300">{{ round($entries->sum('cholesterol'), 2) }}mg</div>
<div class="font-bold border-b border-gray-300">Sodium</div>
<div class="text-right border-b border-gray-300">{{ round($entries->sum('sodium'), 2) }}mg</div>
<div class="font-bold border-b border-gray-300">Carbohydrates</div>
<div class="text-right border-b border-gray-300">{{ round($entries->sum('carbohydrates'), 2) }}g</div>
<div class="font-bold">Protein</div>
<div class="text-right">{{ round($entries->sum('protein'), 2) }}g</div>
<div class="w-full sm:w-5/12 lg:w-4/12">
<div class="flex justify-between items-baseline">
<h3 class="font-semibold text-lg text-gray-800">{{ $date->format('D, j M Y') }}</h3>
<div class="text-gray-700">{{ $entries->count() }} {{ \Illuminate\Support\Pluralizer::plural('entry', $entries->count()) }}</div>
</div>
<div class="text-right border-t-8 border-black text-sm pt-2">% Daily goal</div>
<div class="flex justify-between items-baseline border-b-4 border-black">
<div>
<span class="font-extrabold text-2xl">Calories</span>
<span class="text-lg">{{ number_format($sums['calories']) }}</span>
</div>
<div class="font-extrabold text-right text-lg">
{{ $dailyGoals['calories'] ?? 'N/A' }}
</div>
</div>
<div class="flex justify-between items-baseline border-b border-gray-300 text-sm">
<div>
<span class="font-bold">Fat</span>
{{ number_format($sums['fat']) }}g
</div>
<div class="text-right">
{{ $dailyGoals['fat'] ?? 'N/A' }}
</div>
</div>
<div class="flex justify-between items-baseline border-b border-gray-300 text-sm">
<div>
<span class="font-bold">Cholesterol</span>
{{ number_format($sums['cholesterol']) }}mg
</div>
<div class="text-right">
{{ $dailyGoals['cholesterol'] ?? 'N/A' }}
</div>
</div>
<div class="flex justify-between items-baseline border-b border-gray-300 text-sm">
<div>
<span class="font-bold">Sodium</span>
{{ number_format($sums['sodium']) }}mg
</div>
<div class="text-right">
{{ $dailyGoals['sodium'] ?? 'N/A' }}
</div>
</div>
<div class="flex justify-between items-baseline border-b border-gray-300 text-sm">
<div>
<span class="font-bold">Carbohydrates</span>
{{ number_format($sums['carbohydrates']) }}g
</div>
<div class="text-right">
{{ $dailyGoals['carbohydrates'] ?? 'N/A' }}
</div>
</div>
<div class="flex justify-between items-baseline text-sm">
<div>
<span class="font-bold">Protein</span>
{{ number_format($sums['protein']) }}g
</div>
<div class="text-right">
{{ $dailyGoals['protein'] ?? 'N/A' }}
</div>
</div>
</div>
<div class="w-full sm:w-3/5 md:w-2/3 lg:w-3/4 flex flex-col space-y-4">

View File

@ -62,6 +62,10 @@
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
</div>
<div class="mt-3 space-y-1">
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
</div>
<div class="mt-3 space-y-1">
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}" x-data>

View File

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\FoodController;
use App\Http\Controllers\GoalController;
use App\Http\Controllers\IngredientPickerController;
use App\Http\Controllers\JournalEntryController;
use App\Http\Controllers\RecipeController;
@ -27,8 +28,12 @@ Route::get('/', function (): RedirectResponse {
Route::resource('foods', FoodController::class)->middleware(['auth']);
Route::get('/foods/{food}/delete', [FoodController::class, 'delete'])->middleware(['auth'])->name('foods.delete');
// Recipes.
Route::resource('recipes', RecipeController::class)->middleware(['auth']);
// Goals.
Route::resource('goals', GoalController::class)->middleware(['auth']);
Route::get('/goals/{goal}/delete', [GoalController::class, 'delete'])->middleware(['auth'])->name('goals.delete');
// Ingredient picker.
Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->middleware(['auth'])->name('ingredient-picker.search');
// Journal entries.
Route::get('/journal-entries/create/from-nutrients', [JournalEntryController::class, 'createFromNutrients'])->middleware(['auth'])->name('journal-entries.create.from-nutrients');
@ -36,8 +41,7 @@ Route::post('/journal-entries/create/from-nutrients', [JournalEntryController::c
Route::resource('journal-entries', JournalEntryController::class)->middleware(['auth']);
Route::get('/journal-entries/{journalEntry}/delete', [JournalEntryController::class, 'delete'])->middleware(['auth'])->name('journal-entries.delete');
// Custom.
// TODO: Change this to a custom JSON API endpoint.
Route::get('/ingredient-picker/search', [IngredientPickerController::class, 'search'])->middleware(['auth'])->name('ingredient-picker.search');
// Recipes.
Route::resource('recipes', RecipeController::class)->middleware(['auth']);
require __DIR__.'/auth.php';