diff --git a/app/Filament/Resources/Posts/Tables/PostsTable.php b/app/Filament/Resources/Posts/Tables/PostsTable.php index 714ad28..f8afcb7 100644 --- a/app/Filament/Resources/Posts/Tables/PostsTable.php +++ b/app/Filament/Resources/Posts/Tables/PostsTable.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\Posts\Tables; +use App\Services\NotificationService; use Filament\Actions\Action; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; @@ -56,7 +57,12 @@ class PostsTable ->color('info') ->requiresConfirmation() ->visible(fn ($record) => ! $isAdmin && in_array($record->status, ['draft', 'rejected'])) - ->action(fn ($record) => $record->update(['status' => 'pending', 'rejection_reason' => null])), + ->action(function ($record): void { + $record->update(['status' => 'pending', 'rejection_reason' => null]); + NotificationService::toRole('ketua', 'Artikel Menunggu Persetujuan', + "\"{$record->title}\" oleh {$record->author->name} menunggu persetujuan.", 'warning', + route('filament.admin.resources.posts.edit', $record)); + }), // Untuk admin: approve Action::make('publish') @@ -65,11 +71,15 @@ class PostsTable ->color('success') ->requiresConfirmation() ->visible(fn ($record) => $isAdmin && $record->status === 'pending') - ->action(fn ($record) => $record->update([ + ->action(fn ($record) => tap($record->update([ 'status' => 'published', 'published_at' => now(), 'reviewed_by' => auth()->id(), - ])), + ]), fn () => NotificationService::send( + $record->author, 'Artikel Diterbitkan', + "Artikel \"{$record->title}\" Anda telah diterbitkan.", 'success', + route('filament.admin.resources.posts.edit', $record) + ))), // Untuk admin: tolak Action::make('reject') @@ -80,11 +90,15 @@ class PostsTable ->form([ Textarea::make('rejection_reason')->label('Alasan Penolakan')->required(), ]) - ->action(fn ($record, array $data) => $record->update([ + ->action(fn ($record, array $data) => tap($record->update([ 'status' => 'rejected', 'reviewed_by' => auth()->id(), 'rejection_reason' => $data['rejection_reason'], - ])), + ]), fn () => NotificationService::send( + $record->author, 'Artikel Ditolak', + "Artikel \"{$record->title}\" ditolak: {$data['rejection_reason']}", 'danger', + route('filament.admin.resources.posts.edit', $record) + ))), EditAction::make() ->visible(fn ($record) => $isAdmin || in_array($record->status, ['draft', 'rejected'])), diff --git a/app/Filament/Widgets/ActivityLogWidget.php b/app/Filament/Widgets/ActivityLogWidget.php new file mode 100644 index 0000000..f9ba591 --- /dev/null +++ b/app/Filament/Widgets/ActivityLogWidget.php @@ -0,0 +1,39 @@ +query(ActivityLog::with('user')->latest()->limit(15)) + ->columns([ + TextColumn::make('created_at')->label('Waktu') + ->dateTime('d M Y H:i')->sortable(), + TextColumn::make('user.name')->label('Oleh')->default('Sistem'), + TextColumn::make('action')->label('Aksi')->badge() + ->color(fn ($state) => match ($state) { + 'created' => 'success', + 'verified' => 'info', + 'approved' => 'success', + 'rejected' => 'danger', + 'status_changed' => 'warning', + 'voted' => 'info', + default => 'gray', + }), + TextColumn::make('description')->label('Keterangan')->wrap(), + ]) + ->paginated(false); + } +} diff --git a/app/Observers/ActivityObserver.php b/app/Observers/ActivityObserver.php index 9623487..a8a3ace 100644 --- a/app/Observers/ActivityObserver.php +++ b/app/Observers/ActivityObserver.php @@ -4,6 +4,7 @@ namespace App\Observers; use App\Models\Activity; use App\Models\ActivityLog; +use App\Services\NotificationService; use Illuminate\Support\Facades\Auth; class ActivityObserver @@ -36,6 +37,22 @@ class ActivityObserver 'model_id' => $activity->id, 'description' => "Status kegiatan '{$activity->title}' diubah dari {$old} menjadi {$new}", ]); + + if ($new === 'pending') { + NotificationService::toRole('ketua', 'Kegiatan Menunggu Persetujuan', + "Kegiatan \"{$activity->title}\" diajukan untuk disetujui.", 'warning', + route('filament.admin.resources.activities.edit', $activity)); + } + + if (in_array($new, ['approved', 'rejected'])) { + NotificationService::send( + $activity->creator, + $new === 'approved' ? 'Kegiatan Disetujui' : 'Kegiatan Ditolak', + "Kegiatan \"{$activity->title}\" telah " . ($new === 'approved' ? 'disetujui.' : 'ditolak.'), + $new === 'approved' ? 'success' : 'danger', + route('filament.admin.resources.activities.edit', $activity) + ); + } } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index e38cbd4..dd54f82 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -5,6 +5,7 @@ namespace App\Observers; use App\Models\ActivityLog; use App\Models\MemberStatusLog; use App\Models\User; +use App\Services\NotificationService; use Illuminate\Support\Facades\Auth; class UserObserver @@ -28,6 +29,13 @@ class UserObserver 'model_id' => $user->id, 'description' => "Status anggota {$user->name} diubah dari {$user->getOriginal('status')} menjadi {$user->status}", ]); + + NotificationService::send( + $user, + 'Status Keanggotaan Diubah', + "Status Anda diubah menjadi {$user->status}" . ($user->inactive_reason ? ": {$user->inactive_reason}" : '.'), + $user->status === 'aktif' ? 'success' : 'warning' + ); } } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index eecdbd0..2b65309 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -32,6 +32,7 @@ class AdminPanelProvider extends PanelProvider ->colors([ 'primary' => Color::Amber, ]) + ->databaseNotifications() ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages') ->pages([ @@ -41,6 +42,7 @@ class AdminPanelProvider extends PanelProvider ->widgets([ AccountWidget::class, \App\Filament\Widgets\StatsOverview::class, + \App\Filament\Widgets\ActivityLogWidget::class, ]) ->middleware([ EncryptCookies::class, diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php new file mode 100644 index 0000000..64f814d --- /dev/null +++ b/app/Services/NotificationService.php @@ -0,0 +1,36 @@ +title($title) + ->body($body) + ->color($color); + + if ($url) { + $notification->actions([ + \Filament\Notifications\Actions\Action::make('lihat') + ->label('Lihat') + ->url($url), + ]); + } + + $notification->sendToDatabase( + $recipients instanceof User ? collect([$recipients]) : collect($recipients) + ); + } + + public static function toRole(string $role, string $title, string $body, string $color = 'info', ?string $url = null): void + { + $users = User::role($role)->get(); + if ($users->isEmpty()) return; + self::send($users, $title, $body, $color, $url); + } +} diff --git a/database/migrations/2026_04_03_004642_create_notifications_table.php b/database/migrations/2026_04_03_004642_create_notifications_table.php new file mode 100644 index 0000000..d738032 --- /dev/null +++ b/database/migrations/2026_04_03_004642_create_notifications_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +};