diff --git a/app/Http/Controllers/GoalController.php b/app/Http/Controllers/GoalController.php
new file mode 100644
index 0000000..ad0b02e
--- /dev/null
+++ b/app/Http/Controllers/GoalController.php
@@ -0,0 +1,105 @@
+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!");
+ }
+}
diff --git a/app/Http/Controllers/JournalEntryController.php b/app/Http/Controllers/JournalEntryController.php
index bff0597..e0db472 100644
--- a/app/Http/Controllers/JournalEntryController.php
+++ b/app/Http/Controllers/JournalEntryController.php
@@ -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);
}
/**
diff --git a/app/Models/Goal.php b/app/Models/Goal.php
new file mode 100644
index 0000000..691e6b7
--- /dev/null
+++ b/app/Models/Goal.php
@@ -0,0 +1,102 @@
+ ['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;
+ }
+}
diff --git a/app/Models/JournalEntry.php b/app/Models/JournalEntry.php
index ecdbe34..ad678e7 100644
--- a/app/Models/JournalEntry.php
+++ b/app/Models/JournalEntry.php
@@ -69,7 +69,7 @@ final class JournalEntry extends Model
];
/**
- * The attributes that should be cast.
+ * @inheritdoc
*/
protected $casts = [
'calories' => 'float',
diff --git a/app/Models/User.php b/app/Models/User.php
index 31736f9..36b22c7 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -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;
+ }
}
diff --git a/app/Support/Nutrients.php b/app/Support/Nutrients.php
index 1b030d4..8d4ecda 100644
--- a/app/Support/Nutrients.php
+++ b/app/Support/Nutrients.php
@@ -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.'],
];
/**
diff --git a/database/migrations/2021_02_13_052427_create_goals_table.php b/database/migrations/2021_02_13_052427_create_goals_table.php
new file mode 100644
index 0000000..a46b999
--- /dev/null
+++ b/database/migrations/2021_02_13_052427_create_goals_table.php
@@ -0,0 +1,39 @@
+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');
+ }
+}
diff --git a/resources/views/goals/delete.blade.php b/resources/views/goals/delete.blade.php
new file mode 100644
index 0000000..a44182b
--- /dev/null
+++ b/resources/views/goals/delete.blade.php
@@ -0,0 +1,28 @@
+
+ Delete {{ $goal->goal }} goal?
+
+
+ {{ ($goal->exists ? 'Edit' : 'Add') }} Goal
+
+
+
+
+
+ Add Goal
+
+
+ {{ $goal->summary }}
+
+
+
+
+
+
+
+