Skip to content

Instantly share code, notes, and snippets.

@whoisthisstud
Last active February 25, 2024 16:30
Show Gist options
  • Select an option

  • Save whoisthisstud/1feab97e61b7a8e2bd591332770ed3ec to your computer and use it in GitHub Desktop.

Select an option

Save whoisthisstud/1feab97e61b7a8e2bd591332770ed3ec to your computer and use it in GitHub Desktop.
Laravel Livewire "Dashboard" component + PeriodScopesTrait for dynamic model metric cards
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class Dashboard extends Component
{
/**
* The selected period
*
* Potential options are:
* 'Today', 'Week', 'Month', 'Quarter', 'Year', 'All Time'
*
* Default is 'Year'
*
* @var string $period
*/
public $period = 'Year';
/**
* Function to get current metrics using PeriodScopes Trait.
*
* Pass the classname(-.php) of any model class within App\Models\
* Return metrics based on selected $period
*
* @param string $model
* @return array
*/
protected function getMetrics($model): array
{
if( is_null($model) ) {
return [
'total' => 0,
'change' => 0,
'percentage' => 0,
];
}
$class = "\App\Models\\$model";
$query = $class::query();
$prevQuery = $class::query();
switch ($this->period) {
case 'Today':
$total = $query->today()->count();
$prevTotal = $prevQuery->yesterday()->count();
break;
case 'Week':
$total = $query->thisWeek()->count();
$prevTotal = $prevQuery->previousWeek()->count();
break;
case 'Month':
$total = $query->thisMonth()->count();
$prevTotal = $prevQuery->previousMonth()->count();
break;
case 'Quarter':
$total = $query->thisQuarter()->count();
$prevTotal = $prevQuery->previousQuarter()->count();
break;
case 'Year':
$total = $query->thisYear()->count();
$prevTotal = $prevQuery->previousYear()->count();
break;
default:
$total = $query->count();
$prevTotal = 0;
break;
}
$change = $total - $prevTotal; // period over period
$percentage = ($prevTotal > 0)
? round(($total / $prevTotal) * 100)
: ($total > 0
? 100
: 0);
return [
'total' => $total,
'change' => $change,
'percentage' => $percentage,
];
}
public function render()
{
return view('livewire.dashboard')
->with([
'userMetrics' => $this->getMetrics('User'),
'postMetrics' => $this->getMetrics('Post'),
'commentMetrics' => $this->getMetrics('Comment'),
'salesMetrics' => $this->getMetrics('Sale'),
]);
}
}
<?php
namespace App\Scopes;
use Carbon\Carbon;
trait PeriodScopes {
/**
* Scope a query to include records created today.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeToday($query)
{
$startOfDay = now()->startOfDay();
return $query->whereBetween('created_at', [$startOfDay, now()]);
}
/**
* Scope a query to include records created yesterday.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeYesterday($query)
{
$startOfYesterday = now()->subDay()->startOfDay();
$endOfYesterday = now()->subDay()->endOfDay();
return $query->whereBetween('created_at', [$startOfYesterday, $endOfYesterday]);
}
/**
* Scope a query to include records created this week.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeThisWeek($query)
{
$startOfWeek = now()->startOfWeek(Carbon::SUNDAY)->startOfDay();
return $query->whereBetween('created_at', [$startOfWeek, now()]);
}
/**
* Scope a query to include records created last week.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePreviousWeek($query)
{
$startOfWeek = now()->subDays(7)->startOfWeek(Carbon::SUNDAY)->startOfDay();
$endOfWeek = now()->subDays(7)->endOfWeek(Carbon::SATURDAY)->endOfDay();
return $query->whereBetween('created_at', [$startOfWeek, $endOfWeek]);
}
/**
* Scope a query to include records created this month.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeThisMonth($query)
{
$startOfMonth = now()->startOfMonth()->startOfDay();
return $query->whereBetween('created_at', [$startOfMonth, now()]);
}
/**
* Scope a query to include records created last month.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePreviousMonth($query)
{
$startOfMonth = now()->startOfMonth()->subMonthsNoOverflow()->startOfDay();
$endOfMonth = now()->startOfMonth()->subMonthsNoOverflow()->endOfMonth()->endOfDay();
return $query->whereBetween('created_at', [$startOfMonth, $endOfMonth]);
}
/**
* Scope a query to include records created this quarter.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeThisQuarter($query)
{
$startOfQuarter = now()->firstOfQuarter()->startOfDay();
return $query->whereBetween('created_at', [$startOfQuarter, now()]);
}
/**
* Scope a query to include records created last quarter.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePreviousQuarter($query)
{
$startOfQuarter = now()->firstOfQuarter()->subMonthsNoOverflow(3)->startOfDay();
$endOfQuarter = now()->firstOfQuarter()->subMonthsNoOverflow(3)->lastOfQuarter()->endOfDay();
return $query->whereBetween('created_at', [$startOfQuarter, $endOfQuarter]);
}
/**
* Scope a query to include records created this year.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeThisYear($query)
{
$startOfYear = now()->startOfYear()->startOfDay();
return $query->whereBetween('created_at', [$startOfYear, now()]);
}
/**
* Scope a query to include records created last year.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePreviousYear($query)
{
$startOfYear = now()->startOfYear()->subDays(2)->startOfYear()->startOfDay();
$endOfYear = now()->startOfYear()->subDays(2)->endOfYear()->endOfDay();
return $query->whereBetween('created_at', [$startOfYear, $endOfYear]);
}
}
@props([
'percentage',
'strokeColor'
])
<svg viewBox="0 0 36 36" class="circular-chart">
<path class="circle-chart-bg"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
/>
<path class="circle-chart-fg {{ $strokeColor ?? 'stroke-green-500' }} transition-all duration-75 ease-linear"
stroke-dasharray="{{ $percentage ?? 0 }}, 100"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
/>
<text x="18" y="20.35" class="percentage">{{ $percentage ?? 0 }}%</text>
</svg>
<div class="relative rounded-md bg-white dark:bg-slate-800/20 py-3 px-4 dark:border dark:border-indigo-900/20 sm:py-4 sm:px-6 overflow-visible">
<div wire:loading class="absolute inset-0">
<div wire:loading x-cloak>
<svg class="z-10 absolute top-1 left-1 w-4 h-4 text-indigo-500 animate-spin opacity-80" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
</div>
<div wire:loading wire:loading.absolute class="z-[109] inset-0 bg-white/10 dark:bg-black/[4%] backdrop-blur-[1px] rounded-md"></div>
</div>
{{ $slot }}
<dt>
<div class="flex flex-row justify-start items-center">
{{ $icon }}
<p class="text-sm text-slate-500">{{ $title }}</p>
</div>
</dt>
<dd class="mt-1 flex justify-between items-center">
<div class="flex items-baseline">
<p class="text-2xl font-semibold text-slate-900 dark:text-slate-100">{{ $mainMetric }}</p>
<p class="ml-2 flex items-baseline text-sm font-semibold">
{{ $details }}
</p>
</div>
</dd>
@if ( isset($chart) )
<div class="absolute inset-y-0 w-24 right-4 p-0.5">
{{ $chart }}
</div>
@endif
</div>
<div>
<div class="w-full flex justify-between items-center">
<h3 class="block text-sm font-semibold tracking-tight leading-6 text-indigo-900 dark:text-slate-400">Statistics</h3>
<div>
<select wire:model="period" id="period">
<option value="Today">Today</option>
<option value="Week">Week</option>
<option value="Month">Month</option>
<option value="Quarter">Quarter</option>
<option value="Year">Year</option>
<option value="All Time">All Time</option>
</select>
</div>
</div>
<dl class="mt-2.5 grid grid-cols-1 gap-3 lg:gap-5 2xl:grid-cols-3 overflow-visible">
<!-- start: User Metrics -->
<x-metric-card >
<x-slot:icon>
<!-- User Icon here -->
</x-slot:icon>
<x-slot:title>Total Users</x-slot:title>
<x-slot:mainMetric>
{{ number_format($userMetrics['total'],0) }}
</x-slot:mainMetric>
<x-slot:details>
<div class="{{ $userMetrics['change'] < 0 ? 'text-red-500' : 'text-green-500' }} flex text-[12px]">
<svg class="h-4 w-4 shrink-0 self-center {{ $userMetrics['change'] < 0 ? 'rotate-180' : ($userMetrics['change'] === 0 ? 'hidden' : '') }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" /></svg>
<span class="sr-only"> Increased by </span> {{ $userMetrics['change'] }}
</div>
</x-slot:details>
<x-slot:chart>
<div class="flex w-full h-full self-center">
<x-circle-chart strokeColor="stroke-green-500" percentage="{{ $userMetrics['percentage'] }}" />
</div>
</x-slot:chart>
</x-metric-card>
<!-- end: User Metrics -->
<!-- start: Post Metrics -->
<x-metric-card >
<x-slot:icon>
<!-- Post Icon here -->
</x-slot:icon>
<x-slot:title>Total Posts</x-slot:title>
<x-slot:mainMetric>
{{ number_format($postMetrics['total'],0) }}
</x-slot:mainMetric>
<x-slot:details>
<div class="{{ $postMetrics['change'] < 0 ? 'text-red-500' : 'text-green-500' }} flex text-[12px]">
<svg class="h-4 w-4 shrink-0 self-center {{ $postMetrics['change'] < 0 ? 'rotate-180' : ($postMetrics['change'] === 0 ? 'hidden' : '') }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" /></svg>
<span class="sr-only"> Increased by </span> {{ $postMetrics['change'] }}
</div>
</x-slot:details>
<x-slot:chart>
<div class="flex w-full h-full self-center">
<x-circle-chart strokeColor="stroke-green-500" percentage="{{ $postMetrics['percentage'] }}" />
</div>
</x-slot:chart>
</x-metric-card>
<!-- end: Post Metrics -->
<!-- start: Comment Metrics -->
...
<!-- end: Comment Metrics -->
</dl>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment