Create Widget model and a few widgets
ADD
app/Exceptions/WidgetInvalidPropertyValueException.php
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class WidgetInvalidPropertyValueException extends Exception
+{
+ //
+}

ADD
app/Exceptions/WidgetMissingPropertyException.php
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class WidgetMissingPropertyException extends Exception
+{
+ //
+}

ADD
app/Exceptions/WidgetUnknownTypeException.php
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class WidgetUnknownTypeException extends Exception
+{
+ //
+}

MODIFY
app/Models/User.php
app/Models/User.php
@@ -59,4 +59,9 @@ class User extends Authenticatable
{
return $this->belongsToMany(Space::class, 'user_space')->withPivot('role');
}
+
+ public function widgets()
+ {
+ return $this->hasMany(Widget::class);
+ }
}

ADD
app/Models/Widget.php
+<?php
+
+namespace App\Models;
+
+use App\Exceptions\WidgetInvalidPropertyValueException;
+use App\Exceptions\WidgetMissingPropertyException;
+use App\Exceptions\WidgetUnknownTypeException;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class Widget extends Model
+{
+ use SoftDeletes;
+
+ public function user()
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ /**
+ * Accessors
+ */
+
+ public function getPropertiesAttribute($value)
+ {
+ return json_decode($value);
+ }
+
+ /**
+ * Mutators
+ */
+
+ public function setPropertiesAttribute($value)
+ {
+ /**
+ * Using accessor and mutator for this attribute because the "object" cast
+ * doesn't use JSON_FORCE_OBJECT
+ */
+
+ $this->attributes['properties'] = json_encode($value, JSON_FORCE_OBJECT);
+ }
+
+ /**
+ * Custom
+ */
+ public function render(): string
+ {
+ $widgetClass = '\App\Widgets\\' . ucfirst($this->type);
+
+ if (!class_exists($widgetClass)) {
+ throw new WidgetUnknownTypeException();
+ }
+
+ $requiredProperties = config('widgets.types.' . $this->type . '.properties');
+
+ foreach ($requiredProperties as $requiredPropertyKey => $requiredPropertyOptions) {
+ /**
+ * Check if property exists
+ */
+
+ if (!isset($this->properties->{$requiredPropertyKey})) {
+ throw new WidgetMissingPropertyException();
+ }
+
+ /**
+ * Check if property has valid value
+ */
+
+ if (!in_array($this->properties->{$requiredPropertyKey}, $requiredPropertyOptions)) {
+ throw new WidgetInvalidPropertyValueException();
+ }
+ }
+
+ $widgetInstance = new $widgetClass($this->properties);
+
+ return $widgetInstance->render();
+ }
+}

ADD
app/Widgets/Balance.php
+<?php
+
+namespace App\Widgets;
+
+use App\Helper;
+use App\Models\Space;
+use App\Repositories\DashboardRepository;
+
+class Balance
+{
+ private $dashboardRepository;
+
+ public function __construct()
+ {
+ $this->dashboardRepository = new DashboardRepository();
+ }
+
+ public function render()
+ {
+ $space = Space::find(session('space_id'));
+
+ $currencySymbol = $space->currency->symbol;
+ $balance = $this->dashboardRepository->getBalance(date('Y'), date('n'));
+
+ return view('widgets.balance', [
+ 'currencySymbol' => $currencySymbol,
+ 'balance' => Helper::formatNumber($balance / 100)
+ ]);
+ }
+}

ADD
app/Widgets/Spent.php
+<?php
+
+namespace App\Widgets;
+
+use App\Helper;
+use App\Models\Space;
+use App\Models\Spending;
+
+class Spent
+{
+ private $properties;
+
+ public function __construct(object $properties)
+ {
+ $this->properties = $properties;
+ }
+
+ public function render()
+ {
+ $space = Space::find(session('space_id'));
+
+ $currencySymbol = $space->currency->symbol;
+
+ if ($this->properties->period === 'today') {
+ $spent = Spending::ofSpace(session('space_id'))
+ ->whereRaw('DATE(happened_on) = ?', [date('Y-m-d')])
+ ->sum('amount');
+ }
+
+ if ($this->properties->period === 'this_week') {
+ $monday = date('Y-m-d', strtotime('monday this week'));
+ $sunday = date('Y-m-d', strtotime('sunday this week'));
+
+ $spent = Spending::ofSpace(session('space_id'))
+ ->whereRaw('DATE(happened_on) >= ? AND DATE(happened_ON) <= ?', [$monday, $sunday])
+ ->sum('amount');
+ }
+
+ if ($this->properties->period === 'this_month') {
+ $spent = Spending::ofSpace(session('space_id'))
+ ->whereRaw('YEAR(happened_on) = ? AND MONTH(happened_on) = ?', [date('Y'), date('n')])
+ ->sum('amount');
+ }
+
+ return view('widgets.spent', [
+ 'currencySymbol' => $currencySymbol,
+ 'spent' => Helper::formatNumber($spent / 100),
+ 'period' => str_replace('_', ' ', $this->properties->period)
+ ]);
+ }
+}

ADD
config/widgets.php
+<?php
+
+return [
+ 'types' => [
+ 'balance' => [
+ 'properties' => []
+ ],
+
+ 'spent' => [
+ 'properties' => [
+ 'period' => ['today', 'this_week', 'this_month']
+ ]
+ ],
+
+ // Any other widgets go here
+ ]
+];

ADD
database/migrations/2020_07_28_211805_create_widgets_table.php
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateWidgetsTable extends Migration
+{
+ public function up(): void
+ {
+ Schema::create('widgets', function (Blueprint $table) {
+ $table->id();
+ $table->unsignedInteger('user_id');
+ $table->string('type');
+ $table->unsignedInteger('sorting_index');
+ $table->string('properties');
+ $table->timestamps();
+ $table->softDeletes();
+
+ $table->foreign('user_id')
+ ->references('id')
+ ->on('users');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('widgets');
+ }
+}

ADD
resources/views/widgets/balance.blade.php
+<div class="card card--blue">
+ <h2 style="font-size: 20px;">{!! $currencySymbol !!} {{ $balance }}</h2>
+ <div class="mt-1">Balance</div>
+</div>

ADD
resources/views/widgets/spent.blade.php
+<div class="card card--red">
+ <h2 style="font-size: 20px;">{!! $currencySymbol !!} {{ $spent }}</h2>
+ <div class="mt-1">Spent {{ $period }}</div>
+</div>

Create Widget model and a few widgets