From 8228484cfadc66f06e3dd2373bedc970a28eda0a Mon Sep 17 00:00:00 2001 From: Adamalfantino <158027358+Adamalfantino@users.noreply.github.com> Date: Fri, 22 May 2026 23:56:11 +0700 Subject: [PATCH] feat:update profil --- Procfile | 1 - .../Controllers/Api/DoorAccessController.php | 3 + app/Http/Controllers/Api/PirController.php | 3 + .../Controllers/Api/ReedSwitchController.php | 3 + .../Controllers/Api/VibrationController.php | 3 + app/Models/Device.php | 22 ++ app/Models/User.php | 24 ++ ...00001_add_profile_photo_to_users_table.php | 22 ++ resources/views/dashboard.blade.php | 246 +++++++++++++++++- resources/views/monitoring.blade.php | 20 +- routes/api.php | 14 + routes/web.php | 42 ++- 12 files changed, 388 insertions(+), 15 deletions(-) delete mode 100644 Procfile create mode 100644 database/migrations/2026_05_22_000001_add_profile_photo_to_users_table.php diff --git a/Procfile b/Procfile deleted file mode 100644 index ff2e296..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: php artisan migrate --force && php artisan db:seed --force && frankenphp run --config /Caddyfile diff --git a/app/Http/Controllers/Api/DoorAccessController.php b/app/Http/Controllers/Api/DoorAccessController.php index d347d50..ec9cd6c 100644 --- a/app/Http/Controllers/Api/DoorAccessController.php +++ b/app/Http/Controllers/Api/DoorAccessController.php @@ -102,6 +102,9 @@ public function receiveData(Request $request): JsonResponse 'recorded_at' => $recordedAt ]); + // Tandai device sebagai online + Device::markOnline($data['device_id']); + // Jika akses mencurigakan atau tidak sah, buat alert dan kirim notifikasi $alertSent = false; if ($isSuspicious || !$isAuthorizedAccess || $accessType === 'forced_entry') { diff --git a/app/Http/Controllers/Api/PirController.php b/app/Http/Controllers/Api/PirController.php index 711d8a3..8ee250a 100644 --- a/app/Http/Controllers/Api/PirController.php +++ b/app/Http/Controllers/Api/PirController.php @@ -91,6 +91,9 @@ public function receiveData(Request $request): JsonResponse 'recorded_at' => $recordedAt ]); + // Tandai device sebagai online + Device::markOnline($data['device_id']); + // Jika gerakan mencurigakan, buat alert dan kirim notifikasi $alertSent = false; if ($isSuspicious || $motionType === 'unauthorized') { diff --git a/app/Http/Controllers/Api/ReedSwitchController.php b/app/Http/Controllers/Api/ReedSwitchController.php index 70e6b37..8f0776c 100644 --- a/app/Http/Controllers/Api/ReedSwitchController.php +++ b/app/Http/Controllers/Api/ReedSwitchController.php @@ -112,6 +112,9 @@ public function receiveData(Request $request): JsonResponse 'recorded_at' => $recordedAt ]); + // Tandai device sebagai online + Device::markOnline($data['device_id']); + // Jika akses tidak sah atau pembukaan paksa, buat alert dan kirim notifikasi $alertSent = false; if ($isUnauthorized || $isForcedEntry || $accessLevel !== 'normal') { diff --git a/app/Http/Controllers/Api/VibrationController.php b/app/Http/Controllers/Api/VibrationController.php index 3a60f21..c341108 100644 --- a/app/Http/Controllers/Api/VibrationController.php +++ b/app/Http/Controllers/Api/VibrationController.php @@ -68,6 +68,9 @@ public function receiveData(Request $request): JsonResponse 'recorded_at' => now() ]); + // Tandai device sebagai online + Device::markOnline($data['device_id']); + // Jika getaran abnormal, buat alert dan kirim notifikasi if ($isAbnormal) { $this->handleAbnormalVibration($vibrationReading); diff --git a/app/Models/Device.php b/app/Models/Device.php index d246c36..3c02a5c 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -65,6 +65,28 @@ public function scopeActive($query) return $query->where('is_active', true); } + /** + * Cek apakah device benar-benar online berdasarkan last_seen (dalam 2 menit terakhir) + */ + public function getIsOnlineAttribute(): bool + { + if (!$this->last_seen) { + return false; + } + return $this->last_seen->diffInMinutes(now()) <= 2; + } + + /** + * Update status device ke online dan catat last_seen + */ + public static function markOnline(int $deviceId): void + { + static::where('id', $deviceId)->update([ + 'status' => 'online', + 'last_seen' => now(), + ]); + } + // Accessor untuk status badge color public function getStatusColorAttribute() { diff --git a/app/Models/User.php b/app/Models/User.php index 749c7b7..bfbd5d6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -21,8 +21,32 @@ class User extends Authenticatable 'name', 'email', 'password', + 'profile_photo', ]; + /** + * URL foto profil — fallback ke null jika tidak ada + */ + public function getProfilePhotoUrlAttribute(): ?string + { + if ($this->profile_photo) { + return asset('storage/' . $this->profile_photo); + } + return null; + } + + /** + * Inisial nama untuk avatar fallback + */ + public function getInitialsAttribute(): string + { + $words = explode(' ', trim($this->name)); + if (count($words) >= 2) { + return strtoupper(substr($words[0], 0, 1) . substr($words[1], 0, 1)); + } + return strtoupper(substr($this->name, 0, 2)); + } + /** * The attributes that should be hidden for serialization. * diff --git a/database/migrations/2026_05_22_000001_add_profile_photo_to_users_table.php b/database/migrations/2026_05_22_000001_add_profile_photo_to_users_table.php new file mode 100644 index 0000000..0d54b5d --- /dev/null +++ b/database/migrations/2026_05_22_000001_add_profile_photo_to_users_table.php @@ -0,0 +1,22 @@ +string('profile_photo')->nullable()->after('email'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('profile_photo'); + }); + } +}; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 4b15a0c..7cd11ff 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -118,13 +118,19 @@
-
- +
+@if(auth()->user()->profile_photo) + +@else +
+{{ auth()->user()->initials }} +
+@endif
-

Admin

+

{{ auth()->user()->name }}

System Operator

@@ -152,18 +158,26 @@
-
-
- +
+
+
-
- +
+@if(auth()->user()->profile_photo) + +@else +
+{{ auth()->user()->initials }} +
+@endif
@@ -565,6 +579,98 @@ feather.replace() + + + + + + + \ No newline at end of file diff --git a/resources/views/monitoring.blade.php b/resources/views/monitoring.blade.php index 9c51ea8..ac59c63 100644 --- a/resources/views/monitoring.blade.php +++ b/resources/views/monitoring.blade.php @@ -80,11 +80,17 @@
- +@if(auth()->user()->profile_photo) + +@else +
+{{ auth()->user()->initials }} +
+@endif
-

Admin

+

{{ auth()->user()->name }}

System Operator

@@ -115,11 +121,17 @@
- +@if(auth()->user()->profile_photo) + +@else +
+{{ auth()->user()->initials }} +
+@endif
diff --git a/routes/api.php b/routes/api.php index c6e7f7c..a52b5c1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -79,6 +79,20 @@ Route::post('/process-messages', [LoRaController::class, 'processUnprocessedMessages']); }); +// Device Status API — untuk polling online/offline dari frontend +Route::get('/device/status', function () { + $device = \App\Models\Device::orderBy('last_seen', 'desc')->first(); + $online = $device && $device->last_seen && $device->last_seen->diffInMinutes(now()) <= 2; + + return response()->json([ + 'success' => true, + 'online' => $online, + 'last_seen' => $device?->last_seen?->toISOString(), + 'last_seen_human' => $device?->last_seen?->diffForHumans() ?? 'Belum pernah online', + 'device_name' => $device?->name ?? 'ESP32', + ]); +}); + // Test notification endpoints Route::post('/test-notification', function () { $notificationService = new \App\Services\NotificationService(); diff --git a/routes/web.php b/routes/web.php index 6fc4c22..93ce597 100644 --- a/routes/web.php +++ b/routes/web.php @@ -78,6 +78,41 @@ // Group routes yang memerlukan login Route::middleware(['auth'])->group(function () { + + // Upload foto profil + Route::post('/profile/photo', function (\Illuminate\Http\Request $request) { + $request->validate([ + 'photo' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', + ], [ + 'photo.required' => 'Pilih foto terlebih dahulu.', + 'photo.image' => 'File harus berupa gambar.', + 'photo.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.', + 'photo.max' => 'Ukuran foto maksimal 2MB.', + ]); + + $user = \Illuminate\Support\Facades\Auth::user(); + + // Hapus foto lama jika ada + if ($user->profile_photo) { + \Illuminate\Support\Facades\Storage::disk('public')->delete($user->profile_photo); + } + + $path = $request->file('photo')->store('profile-photos', 'public'); + $user->update(['profile_photo' => $path]); + + return back()->with('success', 'Foto profil berhasil diperbarui.'); + })->name('profile.photo.upload'); + + // Hapus foto profil + Route::delete('/profile/photo', function () { + $user = \Illuminate\Support\Facades\Auth::user(); + if ($user->profile_photo) { + \Illuminate\Support\Facades\Storage::disk('public')->delete($user->profile_photo); + $user->update(['profile_photo' => null]); + } + return back()->with('success', 'Foto profil berhasil dihapus.'); + })->name('profile.photo.delete'); + Route::get('/dashboard', function () { $totalSensor = \App\Models\Sensor::where('is_active', true)->count(); $sensorAktif = \App\Models\Sensor::where('status', 'active')->count(); @@ -100,9 +135,14 @@ $reedAktif = \App\Models\ReedSwitchReading::where('recorded_at', '>=', now()->subSeconds(30)) ->where('door_open', true)->exists(); + // Status device online — berdasarkan last_seen dalam 2 menit terakhir + $device = \App\Models\Device::orderBy('last_seen', 'desc')->first(); + $sistemOnline = $device && $device->last_seen && $device->last_seen->diffInMinutes(now()) <= 2; + return view('dashboard', compact( 'totalSensor', 'sensorAktif', 'peringatanHariIni', 'alertAktif', - 'lastUpdate', 'alertTerbaru', 'pirAktif', 'vibAktif', 'reedAktif' + 'lastUpdate', 'alertTerbaru', 'pirAktif', 'vibAktif', 'reedAktif', + 'device', 'sistemOnline' )); })->name('dashboard');