diff --git a/.env.example b/.env.example index c0660ea..25e7053 100644 --- a/.env.example +++ b/.env.example @@ -1,65 +1,33 @@ -APP_NAME=Laravel +APP_NAME="Aplikasi Ketertiban" APP_ENV=local APP_KEY= APP_DEBUG=true +APP_TIMEZONE=Asia/Jakarta APP_URL=http://localhost -APP_LOCALE=en -APP_FALLBACK_LOCALE=en -APP_FAKER_LOCALE=en_US - -APP_MAINTENANCE_DRIVER=file -# APP_MAINTENANCE_STORE=database - -# PHP_CLI_SERVER_WORKERS=4 - -BCRYPT_ROUNDS=12 - LOG_CHANNEL=stack -LOG_STACK=single -LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug -DB_CONNECTION=sqlite -# DB_HOST=127.0.0.1 -# DB_PORT=3306 -# DB_DATABASE=laravel -# DB_USERNAME=root -# DB_PASSWORD= +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=aplikasi_ketertiban +DB_USERNAME=root +DB_PASSWORD= -SESSION_DRIVER=database -SESSION_LIFETIME=120 -SESSION_ENCRYPT=false -SESSION_PATH=/ -SESSION_DOMAIN=null - -BROADCAST_CONNECTION=log -FILESYSTEM_DISK=local +BROADCAST_CONNECTION=reverb +CACHE_STORE=file QUEUE_CONNECTION=database +SESSION_DRIVER=file +SESSION_LIFETIME=120 -CACHE_STORE=database -# CACHE_PREFIX= +FILESYSTEM_DISK=local -MEMCACHED_HOST=127.0.0.1 +REVERB_APP_ID= +REVERB_APP_KEY= +REVERB_APP_SECRET= +REVERB_HOST=localhost +REVERB_PORT=8080 +REVERB_SCHEME=http -REDIS_CLIENT=phpredis -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - -MAIL_MAILER=log -MAIL_SCHEME=null -MAIL_HOST=127.0.0.1 -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_FROM_ADDRESS="hello@example.com" -MAIL_FROM_NAME="${APP_NAME}" - -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -AWS_DEFAULT_REGION=us-east-1 -AWS_BUCKET= -AWS_USE_PATH_STYLE_ENDPOINT=false - -VITE_APP_NAME="${APP_NAME}" +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index ae02f8b..8275fec 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -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) diff --git a/app/Listeners/CreateViolationNotification.php b/app/Listeners/CreateViolationNotification.php new file mode 100644 index 0000000..4f0dcb1 --- /dev/null +++ b/app/Listeners/CreateViolationNotification.php @@ -0,0 +1,40 @@ +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, + ]); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..7ad9677 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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'); } } diff --git a/app/Services/ViolationService.php b/app/Services/ViolationService.php index eb12d14..c73d338 100644 --- a/app/Services/ViolationService.php +++ b/app/Services/ViolationService.php @@ -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()); } diff --git a/config/app.php b/config/app.php index 423eed5..28be821 100644 --- a/config/app.php +++ b/config/app.php @@ -65,7 +65,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..155d62f --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,56 @@ + env('BROADCAST_CONNECTION', 'reverb'), + + 'connections' => [ + + 'reverb' => [ + 'driver' => 'reverb', + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST', 'localhost'), + 'port' => env('REVERB_PORT', 8087), + 'scheme' => env('REVERB_SCHEME', 'http'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options + ], + ], + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 19fc572..854eae2 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -6,6 +6,7 @@ import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; +window.EchoClass = Echo; // Echo will be initialized when the page has the Reverb config // The actual Echo initialization is done conditionally in the layout diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php index 0a5bc0a..8bef5e2 100644 --- a/resources/views/dashboard/index.blade.php +++ b/resources/views/dashboard/index.blade.php @@ -3,7 +3,7 @@ @section('title', 'Dashboard') @section('content') -
+

Dashboard

Ringkasan pelanggaran siswa

diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index cfca707..2144bd9 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -18,7 +18,7 @@ @stack('styles') -
+
{{-- Sidebar --}}