feat: realtime notifications, fix broadcasting, add notifications page

- Fix: broadcasting.php config (was missing, broadcast failed)
- Fix: Echo class global scope (window.EchoClass)
- Fix: Nginx WebSocket proxy for Reverb
- Fix: Firewall port 8087 opened
- Fix: Duplicate event listener cleanup
- Fix: Mark as read functionality
- Add: CreateViolationNotification listener
- Add: Notifications index page with pagination
- Add: Timezone Asia/Jakarta support
- Add: Real-time badge across all pages
- Remove: toOthers() so recorder also gets notification
- Update: .env.example with new variables
This commit is contained in:
Novy San87
2026-07-22 08:35:10 +07:00
parent 9670ae06d3
commit 23f124e8d5
13 changed files with 273 additions and 79 deletions
@@ -72,6 +72,15 @@ class DashboardController extends Controller
return response()->json(['success' => true]);
}
public function notificationsIndex(Request $request): View
{
$notifications = AppNotification::where('user_id', $request->user()->id)
->latest()
->paginate(20);
return view('notifications.index', compact('notifications'));
}
public function getUnreadCount(Request $request)
{
$count = AppNotification::where('user_id', $request->user()->id)
@@ -0,0 +1,40 @@
<?php
namespace App\Listeners;
use App\Events\ViolationRecorded;
use App\Models\AppNotification;
use App\Models\User;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class CreateViolationNotification implements ShouldQueue
{
use InteractsWithQueue;
public function handle(ViolationRecorded $event): void
{
$violation = $event->violation;
$data = $event->data;
// Kirim notifikasi ke semua user kecuali pencatat
$users = User::where('is_active', true)
->where('id', '!=', $violation->recorded_by)
->get();
foreach ($users as $user) {
AppNotification::create([
'user_id' => $user->id,
'type' => 'violation',
'title' => 'Pelanggaran Baru: ' . ($data['student_name'] ?? ''),
'body' => ($data['violation_type'] ?? '') . ' (+' . ($data['points'] ?? 0) . ' poin)',
'icon' => 'exclamation-triangle',
'color' => $data['category_color'] ?? '#ef4444',
'action_url' => $data['url'] ?? null,
'is_read' => false,
'violation_id' => $violation->id,
'data' => $data,
]);
}
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -19,6 +20,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
//
Paginator::defaultView('vendor.pagination.tailwind');
}
}
+1 -1
View File
@@ -127,7 +127,7 @@ class ViolationService
}
// Broadcast via Reverb
broadcast(new ViolationRecorded($violation))->toOthers();
broadcast(new ViolationRecorded($violation));
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Notify realtime failed: ' . $e->getMessage());
}