Skip to content

Commit d2a9c76

Browse files
authored
fix: dashboard stats performance (#622)
1 parent 7048950 commit d2a9c76

7 files changed

Lines changed: 259 additions & 275 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Exceptions;
6+
7+
use Exception;
8+
9+
class InvalidDataObjectException extends Exception
10+
{
11+
protected $message = 'The data object must have a "current" and "previous" property.';
12+
}

app/Filament/Widgets/Components/Stat.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
namespace App\Filament\Widgets\Components;
66

7-
use Exception;
7+
use App\Exceptions\InvalidDataObjectException;
88
use Filament\Support\Icons\Heroicon;
99
use Filament\Widgets\StatsOverviewWidget\Stat as BaseStat;
1010
use Illuminate\Contracts\Support\Htmlable;
11+
use Illuminate\Support\Number;
1112

1213
class Stat extends BaseStat
1314
{
@@ -19,7 +20,7 @@ public static function make(string | Htmlable $label, $value = null): static
1920
public function trend(object $data, int $precision = 0): static
2021
{
2122
if (! property_exists($data, 'current') || ! property_exists($data, 'previous')) {
22-
throw new Exception('The data object must have a "current" and "previous" property.');
23+
throw new InvalidDataObjectException;
2324
}
2425

2526
$this->value($data->current);
@@ -46,4 +47,19 @@ public function trend(object $data, int $precision = 0): static
4647

4748
return $this;
4849
}
50+
51+
public function getValue(): mixed
52+
{
53+
$value = parent::getValue();
54+
55+
if (\is_int($value)) {
56+
return Number::format($value, precision: 0);
57+
}
58+
59+
if (\is_float($value)) {
60+
return Number::format($value, precision: 2);
61+
}
62+
63+
return $value;
64+
}
4965
}

app/Filament/Widgets/StatsWidgets/AdminStatsWidget.php

Lines changed: 28 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,15 @@
66

77
use App\Filament\Resources\Users\UserResource;
88
use App\Filament\Widgets\Components\Stat;
9-
use App\Models\Appointment;
10-
use App\Models\Beneficiary;
11-
use App\Models\Intervention;
12-
use App\Models\User;
13-
use App\Services\StatCount;
149
use Filament\Support\Icons\Heroicon;
15-
use Filament\Widgets\StatsOverviewWidget;
1610

1711
class AdminStatsWidget extends StatsOverviewWidget
1812
{
19-
protected static ?int $sort = 0;
20-
21-
protected ?string $pollingInterval = null;
22-
2313
public static function canView(): bool
2414
{
2515
return auth()->user()->isAdmin();
2616
}
2717

28-
public function getHeading(): string
29-
{
30-
return __('dashboard.stats.heading');
31-
}
32-
3318
protected function getColumns(): int|array
3419
{
3520
return [
@@ -40,70 +25,35 @@ protected function getColumns(): int|array
4025

4126
protected function getStats(): array
4227
{
28+
$stats = static::cache('admin-stats-widget', fn (): array => [
29+
'appointments' => static::appointmentsTrend(),
30+
'nurses_total' => static::allNursesCount(),
31+
'beneficiaries_total' => static::allBeneficiariesCount(),
32+
'beneficiaries_active' => static::activeBeneficiariesCount(),
33+
'services' => static::realizedServicesTrend(),
34+
]);
35+
4336
return [
44-
$this->getAppointmentsStat(),
45-
$this->getAllNursesStat(),
46-
$this->getAllBeneficiariesStat(),
47-
$this->getActiveBeneficiariesStat(),
48-
$this->getRealizedServicesStat(),
37+
Stat::make(__('dashboard.stats.appointments'))
38+
->icon(Heroicon::Calendar)
39+
->trend($stats['appointments']),
40+
41+
Stat::make(__('dashboard.stats.nurses_total'))
42+
->icon(Heroicon::UserGroup)
43+
->value($stats['nurses_total'])
44+
->url(UserResource::getUrl('index')),
45+
46+
Stat::make(__('dashboard.stats.beneficiaries_total'))
47+
->icon(Heroicon::UserGroup)
48+
->value($stats['beneficiaries_total']),
49+
50+
Stat::make(__('dashboard.stats.beneficiaries_active'))
51+
->icon(Heroicon::Users)
52+
->value($stats['beneficiaries_active']),
53+
54+
Stat::make(__('dashboard.stats.services'))
55+
->icon(Heroicon::Bolt)
56+
->trend($stats['services']),
4957
];
5058
}
51-
52-
private function getAppointmentsStat(): Stat
53-
{
54-
$value = Appointment::select(StatCount::comparedBy('date'))
55-
->toBase()
56-
->first();
57-
58-
return Stat::make(__('dashboard.stats.appointments'))
59-
->icon(Heroicon::Calendar)
60-
->trend($value);
61-
}
62-
63-
private function getAllNursesStat(): Stat
64-
{
65-
$value = User::query()
66-
->onlyNurses()
67-
->count();
68-
69-
$url = UserResource::getUrl('index');
70-
71-
return Stat::make(__('dashboard.stats.nurses_total'))
72-
->icon(Heroicon::UserGroup)
73-
->value($value)
74-
->url($url);
75-
}
76-
77-
private function getAllBeneficiariesStat(): Stat
78-
{
79-
$value = Beneficiary::count();
80-
81-
return Stat::make(__('dashboard.stats.beneficiaries_total'))
82-
->icon(Heroicon::UserGroup)
83-
->value($value);
84-
}
85-
86-
private function getActiveBeneficiariesStat(): Stat
87-
{
88-
$value = Beneficiary::query()
89-
->onlyActive()
90-
->count();
91-
92-
return Stat::make(__('dashboard.stats.beneficiaries_active'))
93-
->icon(Heroicon::Users)
94-
->value($value);
95-
}
96-
97-
private function getRealizedServicesStat(): Stat
98-
{
99-
$value = Intervention::select(StatCount::comparedBy('closed_at'))
100-
->onlyIndividualServices()
101-
->onlyRealized()
102-
->toBase()
103-
->first();
104-
105-
return Stat::make(__('dashboard.stats.services'))
106-
->icon(Heroicon::Bolt)
107-
->trend($value);
108-
}
10959
}

app/Filament/Widgets/StatsWidgets/CoordinatorStatsWidget.php

Lines changed: 30 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,15 @@
66

77
use App\Filament\Resources\Users\UserResource;
88
use App\Filament\Widgets\Components\Stat;
9-
use App\Models\Appointment;
10-
use App\Models\Beneficiary;
11-
use App\Models\Intervention;
12-
use App\Models\User;
13-
use App\Services\StatCount;
149
use Filament\Support\Icons\Heroicon;
15-
use Filament\Widgets\StatsOverviewWidget;
1610

1711
class CoordinatorStatsWidget extends StatsOverviewWidget
1812
{
19-
protected static ?int $sort = 0;
20-
21-
protected ?string $pollingInterval = null;
22-
2313
public static function canView(): bool
2414
{
2515
return auth()->user()->isCoordinator();
2616
}
2717

28-
public function getHeading(): string
29-
{
30-
return __('dashboard.stats.heading');
31-
}
32-
3318
protected function getColumns(): int|array
3419
{
3520
return [
@@ -40,71 +25,37 @@ protected function getColumns(): int|array
4025

4126
protected function getStats(): array
4227
{
43-
return [
44-
$this->getAppointmentsStat(),
45-
$this->getAllNursesStat(),
46-
$this->getAllBeneficiariesStat(),
47-
$this->getActiveBeneficiariesStat(),
48-
$this->getRealizedServicesStat(),
49-
];
50-
}
51-
52-
private function getAppointmentsStat(): Stat
53-
{
54-
$value = Appointment::select(StatCount::comparedBy('date'))
55-
->toBase()
56-
->first();
57-
58-
return Stat::make(__('dashboard.stats.appointments'))
59-
->icon(Heroicon::Calendar)
60-
->trend($value);
61-
}
62-
63-
private function getAllNursesStat(): Stat
64-
{
65-
$value = User::query()
66-
->onlyNurses()
67-
->activatesInCounty(auth()->user()->county_id)
68-
->count();
28+
$countyId = auth()->user()->county_id;
6929

70-
$url = UserResource::getUrl('index');
30+
$stats = static::cache("coordinator-stats-widget:county:$countyId", fn (): array => [
31+
'appointments' => static::appointmentsTrend(),
32+
'nurses_total' => static::allNursesCount($countyId),
33+
'beneficiaries_total' => static::allBeneficiariesCount(),
34+
'beneficiaries_active' => static::activeBeneficiariesCount(),
35+
'services' => static::realizedServicesTrend(),
36+
]);
7137

72-
return Stat::make(__('dashboard.stats.nurses_total'))
73-
->icon(Heroicon::UserGroup)
74-
->value($value)
75-
->url($url);
76-
}
77-
78-
private function getAllBeneficiariesStat(): Stat
79-
{
80-
$value = Beneficiary::count();
81-
82-
return Stat::make(__('dashboard.stats.beneficiaries_total'))
83-
->icon(Heroicon::UserGroup)
84-
->value($value);
85-
}
86-
87-
private function getActiveBeneficiariesStat(): Stat
88-
{
89-
$value = Beneficiary::query()
90-
->onlyActive()
91-
->count();
92-
93-
return Stat::make(__('dashboard.stats.beneficiaries_active'))
94-
->icon(Heroicon::Users)
95-
->value($value);
96-
}
97-
98-
private function getRealizedServicesStat(): Stat
99-
{
100-
$value = Intervention::select(StatCount::comparedBy('closed_at'))
101-
->onlyIndividualServices()
102-
->onlyRealized()
103-
->toBase()
104-
->first();
105-
106-
return Stat::make(__('dashboard.stats.services'))
107-
->icon(Heroicon::Bolt)
108-
->trend($value);
38+
return [
39+
Stat::make(__('dashboard.stats.appointments'))
40+
->icon(Heroicon::Calendar)
41+
->trend($stats['appointments']),
42+
43+
Stat::make(__('dashboard.stats.nurses_total'))
44+
->icon(Heroicon::UserGroup)
45+
->value($stats['nurses_total'])
46+
->url(UserResource::getUrl('index')),
47+
48+
Stat::make(__('dashboard.stats.beneficiaries_total'))
49+
->icon(Heroicon::UserGroup)
50+
->value($stats['beneficiaries_total']),
51+
52+
Stat::make(__('dashboard.stats.beneficiaries_active'))
53+
->icon(Heroicon::Users)
54+
->value($stats['beneficiaries_active']),
55+
56+
Stat::make(__('dashboard.stats.services'))
57+
->icon(Heroicon::Bolt)
58+
->trend($stats['services']),
59+
];
10960
}
11061
}

0 commit comments

Comments
 (0)