Skip to content

Commit

Permalink
feat(admin): improve UX and implement dashboard (#62)
Browse files Browse the repository at this point in the history
* feat(admin): improve login/logout

* feat(admin): show user avatar

* feat(ui): add frontdesk and admin links to dashboard

* feat(admin): add statistics widgets to dashboard

* feat(admin): use caching for stats

* feat(admin): configure fixed widgets instead of discovery

* feat(admin): requested/assigned tables widget

* chore: add display name utility to ApplicationStatus

* feat(admin): exclude waiting from active on dashboard

* feat(admin): add info widget to dashboard

* feat(admin): add registration status chart to dashboard

* fix(admin): properly cache regsys data
  • Loading branch information
Fenrikur authored Feb 21, 2024
1 parent 89c120a commit 7498cd1
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 9 deletions.
5 changes: 5 additions & 0 deletions app/Enums/ApplicationStatus.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

namespace App\Enums;
use Illuminate\Support\Str;

enum ApplicationStatus: string
{
Expand Down Expand Up @@ -157,4 +158,8 @@ function orWhere(\Illuminate\Database\Eloquent\Builder $query)
),
};
}

function displayName(): string {
return Str::of($this->value)->replace('_', ' ')->title()->value();
}
}
58 changes: 58 additions & 0 deletions app/Filament/Widgets/AbstractApplicationTablesChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace App\Filament\Widgets;

use App\Models\Application;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

abstract class AbstractApplicationTablesChart extends ChartWidget
{
protected static ?string $heading = 'Total Tables (active)';
protected static ?string $maxHeight = '200px';

protected function getType(): string
{
return 'pie';
}

abstract protected function retrieveData(): \Illuminate\Support\Collection;

protected function getData(): array
{
$data = $this->retrieveData();
return [
'datasets' => [
[
'label' => 'Total Tables',
'data' => $data->pluck('count')->all(),
'backgroundColor' => [
'rgb(200, 180, 90)',
'rgb(0, 200, 255)',
'rgb(255, 200, 80)',
'rgb(250, 100, 200)',
'rgb(150, 100, 255)',
'rgb(0, 180, 0)',
'rgb(250, 80, 50)',
],
],
],
'labels' => $data->pluck('type')->all(),
];
}

protected function getOptions(): array
{
return [
'scales' => [
'x' => [
'display' => false,
],
'y' => [
'display' => false,
],
],
];
}
}
23 changes: 23 additions & 0 deletions app/Filament/Widgets/ApplicationStats.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Filament\Widgets;

use App\Models\Application;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class ApplicationStats extends BaseWidget
{
protected function getStats(): array
{
$data = Cache::remember('dd-admin-application-stats', 60, fn() => Application::query()->toBase()->select(DB::raw('type, COUNT(*) as count'))->whereNull('canceled_at')->whereNull('waiting_at')->groupBy('type')->get());
return [
Stat::make('Total Applications (active)', fn() => $data->sum('count')),
Stat::make('Total Dealers (active)', fn() => $data->firstWhere('type', 'dealer')?->count ?? 0),
Stat::make('Total Shares (active)', fn() => $data->firstWhere('type', 'share')?->count ?? 0),
Stat::make('Total Assistants (active)', fn() => $data->firstWhere('type', 'assistant')?->count ?? 0),
];
}
}
67 changes: 67 additions & 0 deletions app/Filament/Widgets/ApplicationStatusChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace App\Filament\Widgets;

use App\Enums\ApplicationStatus;
use App\Models\Application;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Facades\Cache;

class ApplicationStatusChart extends ChartWidget
{
protected static ?string $heading = 'Applications by Status';
protected static ?string $maxHeight = '200px';

protected function getType(): string
{
return 'pie';
}

protected function getData(): array
{
$status = Cache::remember('dd-admin-application-status', 60, fn () => [
ApplicationStatus::Canceled->displayName() => ApplicationStatus::Canceled->orWhere(Application::query())->count(),
ApplicationStatus::Open->displayName() => ApplicationStatus::Open->orWhere(Application::query())->count(),
ApplicationStatus::Waiting->displayName() => ApplicationStatus::Waiting->orWhere(Application::query())->count(),
ApplicationStatus::TableAssigned->displayName() => ApplicationStatus::TableAssigned->orWhere(Application::query())->count(),
ApplicationStatus::TableOffered->displayName() => ApplicationStatus::TableOffered->orWhere(Application::query())->count(),
ApplicationStatus::TableAccepted->displayName() => ApplicationStatus::TableAccepted->orWhere(Application::query())->count(),
ApplicationStatus::CheckedIn->displayName() => ApplicationStatus::CheckedIn->orWhere(Application::query())->count(),
ApplicationStatus::CheckedOut->displayName() => ApplicationStatus::CheckedOut->orWhere(Application::query())->count(),
]);

return [
'datasets' => [
[
'label' => 'Total Applications',
'data' => array_values($status),
'backgroundColor' => [
'rgb(250, 80, 80)',
'rgb(0, 200, 255)',
'rgb(255, 200, 80)',
'rgb(250, 100, 200)',
'rgb(150, 100, 255)',
'rgb(100, 255, 100)',
'rgb(0, 180, 0)',
'rgb(120, 120, 120)',
],
],
],
'labels' => array_keys($status),
];
}

protected function getOptions(): array
{
return [
'scales' => [
'x' => [
'display' => false,
],
'y' => [
'display' => false,
],
],
];
}
}
17 changes: 17 additions & 0 deletions app/Filament/Widgets/ApplicationTablesAssignedChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Filament\Widgets;

use App\Models\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class ApplicationTablesAssignedChart extends AbstractApplicationTablesChart
{
protected static ?string $heading = 'Total Tables Assigned (active)';

protected function retrieveData(): \Illuminate\Support\Collection
{
return Cache::remember('dd-admin-application-tables-assigned', 60, fn() => Application::query()->toBase()->join('table_types', 'table_type_assigned', '=', 'table_types.id')->select(DB::raw('COUNT(*) as count, name as type'))->where('type', '=', 'dealer')->whereNull('canceled_at')->whereNull('waiting_at')->groupBy('name')->get());
}
}
17 changes: 17 additions & 0 deletions app/Filament/Widgets/ApplicationTablesRequestedChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Filament\Widgets;

use App\Models\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class ApplicationTablesRequestedChart extends AbstractApplicationTablesChart
{
protected static ?string $heading = 'Total Tables Requested (active)';

protected function retrieveData(): \Illuminate\Support\Collection
{
return Cache::remember('dd-admin-application-tables-requested', 60, fn() => Application::query()->toBase()->join('table_types', 'table_type_requested', '=', 'table_types.id')->select(DB::raw('COUNT(*) as count, name as type'))->where('type', '=', 'dealer')->whereNull('canceled_at')->whereNull('waiting_at')->groupBy('name')->get());
}
}
11 changes: 11 additions & 0 deletions app/Filament/Widgets/DashboardInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Filament\Widgets;

use Filament\Widgets\Widget;

class DashboardInfo extends Widget
{
protected static string $view = 'filament.widgets.dashboard-info';
protected int | string | array $columnSpan = 'full';
}
74 changes: 74 additions & 0 deletions app/Filament/Widgets/RegistrationStatusChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace App\Filament\Widgets;

use App\Http\Controllers\Client\RegSysClientController;
use App\Models\Application;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class RegistrationStatusChart extends ChartWidget
{
protected static ?string $heading = 'Registration Status (active; refresh: 10 minutes)';
protected static ?string $pollingInterval = '3600s';
protected static ?string $maxHeight = '200px';

protected function getType(): string
{
return 'pie';
}

protected function getData(): array
{
$data = Cache::remember('dd-admin-application-totals', 10 * 60, function (): array {
$registrations = RegSysClientController::getAllRegs('id');
return Application::with('user')->whereNull('canceled_at')->whereNull('waiting_at')->get()
->map(fn (Application $application): string => $registrations[$application->user_id]['status'] ?? 'unknown')
->reduce(function (array $statusCount, string $status) {
$statusCount[$status] += 1;
return $statusCount;
}, [
'new' => 0,
'approved' => 0,
'partially paid' => 0,
'paid' => 0,
'checked in' => 0,
'unknown' => 0,
'cancelled' => 0,
]);
});
return [
'datasets' => [
[
'label' => 'Total Applications',
'data' => array_values($data),
'backgroundColor' => [
'rgb(255, 200, 80)',
'rgb(0, 200, 255)',
'rgb(250, 100, 200)',
'rgb(100, 255, 100)',
'rgb(0, 180, 0)',
'rgb(120, 120, 120)',
'rgb(250, 80, 80)',
],
],
],
'labels' => array_keys($data),
];
}

protected function getOptions(): array
{
return [
'scales' => [
'x' => [
'display' => false,
],
'y' => [
'display' => false,
],
],
];
}
}
9 changes: 8 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Session;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable implements FilamentUser
class User extends Authenticatable implements FilamentUser, HasAvatar
{
use HasApiTokens, HasFactory, Notifiable;
/**
Expand Down Expand Up @@ -81,4 +83,9 @@ public function canAccessPanel(Panel $panel): bool
{
return $this->isAdmin();
}

public function getFilamentAvatarUrl(): ?string
{
return Session::get('avatar');
}
}
20 changes: 13 additions & 7 deletions app/Providers/Filament/AdminPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace App\Providers\Filament;

use Filament\Http\Middleware\Authenticate;
use App\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Navigation\NavigationGroup;
use Filament\Navigation\MenuItem;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
Expand All @@ -27,7 +27,9 @@ public function panel(Panel $panel): Panel
->default()
->id('admin')
->path('admin')
->login()
->userMenuItems([
'logout' => MenuItem::make()->label('Log out')->url(fn () => route('auth.frontchannel-logout')),
])
->colors([
'primary' => Color::Amber,
])
Expand All @@ -36,10 +38,14 @@ public function panel(Panel $panel): Panel
->pages([
Pages\Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
//->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
\App\Filament\Widgets\DashboardInfo::class,
\App\Filament\Widgets\ApplicationStats::class,
\App\Filament\Widgets\ApplicationStatusChart::class,
\App\Filament\Widgets\RegistrationStatusChart::class,
\App\Filament\Widgets\ApplicationTablesRequestedChart::class,
\App\Filament\Widgets\ApplicationTablesAssignedChart::class,
])
->middleware([
EncryptCookies::class,
Expand All @@ -56,4 +62,4 @@ public function panel(Panel $panel): Panel
Authenticate::class,
]);
}
}
}
Loading

0 comments on commit 7498cd1

Please sign in to comment.