diff --git a/app/Filament/Widgets/DashboardStatsWidget.php b/app/Filament/Widgets/DashboardStatsWidget.php new file mode 100644 index 0000000..8c8f35a --- /dev/null +++ b/app/Filament/Widgets/DashboardStatsWidget.php @@ -0,0 +1,48 @@ +count(); + $activeMemberships = Membership::where('status', 'active')->count(); + $newThisMonth = Membership::whereMonth('created_at', now()->month) + ->whereYear('created_at', now()->year) + ->count(); + $unpaidMemberships = Membership::where('payment_status', 'unpaid') + ->where('status', 'active') + ->count(); + + return [ + Stat::make('Membres actifs', $activeMembers) + ->description('Statut "Valide"') + ->descriptionIcon('heroicon-o-user-group', IconPosition::Before) + ->color('success'), + + Stat::make('Adhésions actives', $activeMemberships) + ->description('En cours') + ->descriptionIcon('heroicon-o-identification', IconPosition::Before) + ->color('primary'), + + Stat::make('Nouvelles adhésions', $newThisMonth) + ->description('Ce mois-ci') + ->descriptionIcon('heroicon-o-calendar', IconPosition::Before) + ->color('info'), + + Stat::make('Paiements en attente', $unpaidMemberships) + ->description('Adhésions actives non réglées') + ->descriptionIcon('heroicon-o-banknotes', IconPosition::Before) + ->color($unpaidMemberships > 0 ? 'warning' : 'success'), + ]; + } +} diff --git a/app/Filament/Widgets/LatestMembershipsWidget.php b/app/Filament/Widgets/LatestMembershipsWidget.php new file mode 100644 index 0000000..da4ae16 --- /dev/null +++ b/app/Filament/Widgets/LatestMembershipsWidget.php @@ -0,0 +1,77 @@ +query( + fn (): Builder => Membership::query() + ->with(['member', 'package']) + ->latest() + ->limit(8) + ) + ->columns([ + TextColumn::make('member.full_name') + ->label('Adhérent') + ->searchable(['members.firstname', 'members.lastname']), + + TextColumn::make('package.name') + ->label('Package'), + + TextColumn::make('status') + ->label('Statut adhésion') + ->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state)) + ->badge() + ->color(fn (string $state): string => match ($state) { + 'active' => 'success', + 'expired' => 'danger', + 'pending' => 'warning', + default => 'gray', + }), + + TextColumn::make('payment_status') + ->label('Paiement') + ->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state)) + ->badge() + ->color(fn (string $state): string => match ($state) { + 'paid' => 'success', + 'unpaid' => 'danger', + 'partial' => 'warning', + default => 'gray', + }), + + TextColumn::make('amount') + ->label('Montant') + ->money('EUR'), + + TextColumn::make('created_at') + ->label('Créée le') + ->dateTime('d/m/Y') + ->sortable(), + ]) + ->recordActions([ + Action::make('edit') + ->label('Voir') + ->icon('heroicon-o-pencil-square') + ->url(fn (Membership $record): string => MembershipResource::getUrl('edit', ['record' => $record])), + ]) + ->paginated(false); + } +} diff --git a/app/Filament/Widgets/MembershipsPerMonthChart.php b/app/Filament/Widgets/MembershipsPerMonthChart.php new file mode 100644 index 0000000..fa87e66 --- /dev/null +++ b/app/Filament/Widgets/MembershipsPerMonthChart.php @@ -0,0 +1,50 @@ +map(function (int $monthsAgo) { + $date = now()->subMonths($monthsAgo); + + return [ + 'month' => $date->translatedFormat('M Y'), + 'count' => Membership::whereYear('created_at', $date->year) + ->whereMonth('created_at', $date->month) + ->count(), + ]; + }); + + return [ + 'datasets' => [ + [ + 'label' => 'Adhésions', + 'data' => $data->pluck('count')->toArray(), + 'borderColor' => 'rgb(244, 63, 94)', + 'backgroundColor' => 'rgba(244, 63, 94, 0.1)', + 'fill' => true, + 'tension' => 0.4, + ], + ], + 'labels' => $data->pluck('month')->toArray(), + ]; + } + + protected function getType(): string + { + return 'line'; + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index b891847..8a0a9ff 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -3,8 +3,9 @@ namespace App\Providers\Filament; use Andreia\FilamentNordTheme\FilamentNordThemePlugin; -use App\Filament\Resources\Members\Widgets\MemberCount; -use App\Filament\Resources\Memberships\Widgets\MembershipsChart; +use App\Filament\Widgets\DashboardStatsWidget; +use App\Filament\Widgets\LatestMembershipsWidget; +use App\Filament\Widgets\MembershipsPerMonthChart; use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; @@ -14,8 +15,7 @@ use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; -use Filament\Widgets\AccountWidget; -use Filament\Widgets\FilamentInfoWidget; +use Filament\View\PanelsRenderHook; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; @@ -46,10 +46,9 @@ class AdminPanelProvider extends PanelProvider ]) ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets') ->widgets([ - AccountWidget::class, - FilamentInfoWidget::class, - MemberCount::class, - // MembershipsChart::class, + DashboardStatsWidget::class, + MembershipsPerMonthChart::class, + LatestMembershipsWidget::class, ]) ->middleware([ EncryptCookies::class, @@ -68,6 +67,10 @@ class AdminPanelProvider extends PanelProvider ]) ->authMiddleware([ Authenticate::class, - ]); + ]) + ->renderHook( + PanelsRenderHook::GLOBAL_SEARCH_BEFORE, + fn () => view('filament.components.visit-site-button'), + ); } } diff --git a/resources/views/filament/components/visit-site-button.blade.php b/resources/views/filament/components/visit-site-button.blade.php new file mode 100644 index 0000000..ca3d069 --- /dev/null +++ b/resources/views/filament/components/visit-site-button.blade.php @@ -0,0 +1,9 @@ + + + Voir le site +