Log Aktivitas Pengguna
+Pantau seluruh aktivitas yang dilakukan oleh pengguna secara real-time
+diff --git a/app.py b/app.py index acb196e..f1b4f44 100644 --- a/app.py +++ b/app.py @@ -1,65 +1,45 @@ from flask import Flask, request, jsonify -from flask_cors import CORS -from sklearn.feature_extraction.text import TfidfVectorizer -from sklearn.naive_bayes import MultinomialNB -import numpy as np +from groq import Groq app = Flask(__name__) -CORS(app) # Agar bisa diakses dari HTML berbeda domain/port -# 1. DATA LATIH (Training Data) -# Kita ajari bot dengan contoh kalimat dan labelnya -data_latih = [ - ("halo", "salam"), - ("selamat pagi", "salam"), - ("berapa harga rumah", "harga"), - ("biaya cicilan mahal gak", "harga"), - ("saya mau kredit rumah", "kpr"), - ("proses KPR gimana", "kpr"), - ("lokasi kantor dimana", "lokasi"), - ("alamat properti di mana", "lokasi") -] +client = Groq( + api_key="API_KEY_GROQ_KAMU" +) -texts, labels = zip(*data_latih) - -# 2. PROSES TRAINING (Mesin Belajar) -# Mengubah teks jadi angka (Vector) -vectorizer = TfidfVectorizer() -X = vectorizer.fit_transform(texts) -y = np.array(labels) - -# Melatih model Naive Bayes -model = MultinomialNB() -model.fit(X, y) - -# 3. API ENDPOINT (Tempat Browser Menghubungi) @app.route('/chat', methods=['POST']) def chat(): - user_input = request.json.get('message') - - # Ubah input user jadi vektor sesuai pola training - user_vector = vectorizer.transform([user_input]) - - # Prediksi intent - prediksi = model.predict(user_vector)[0] - confidence = max(model.predict_proba(user_vector)[0]) - - # Logika Respon Berdasarkan Hasil Prediksi ML - respon = "" - if confidence < 0.5: # Jika model kurang yakin - respon = "Maaf, saya kurang mengerti. Bisa diulang dengan bahasa lain?" - elif prediksi == 'salam': - respon = "Halo! Selamat datang di PropertiHarmoni. Ada yang bisa dibantu? 😊" - elif prediksi == 'harga': - respon = "Harga kami bervariasi mulai 500jt hingga 3M. Mau cari di range berapa?" - elif prediksi == 'kpr': - respon = "Untuk KPR, kami kerjasama dengan BCA, Mandiri, dan BNI. Mau simulasi cicilan?" - elif prediksi == 'lokasi': - respon = "Kantor kami ada di Jl. Raya Malang No. 10. Mau kirim Google Maps-nya?" - else: - respon = "Mohon maaf, tim kami akan segera membalas pertanyaan spesifik Anda." - return jsonify({'response': respon, 'intent': prediksi}) + data = request.json + user_message = data.get("message") -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file + completion = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + { + "role": "system", + "content": """ + Kamu adalah CaraniBot, asisten properti yang ramah, santai, dan profesional. + Jawab dengan bahasa Indonesia natural. + """ + }, + { + "role": "user", + "content": user_message + } + ] + ) + + reply = completion.choices[0].message.content + + return jsonify({ + "reply": reply + }) + + +if __name__ == "__main__": + app.run( + host="127.0.0.1", + port=5000, + debug=True + ) \ No newline at end of file diff --git a/app/Helpers/StatusHelper.php b/app/Helpers/StatusHelper.php new file mode 100644 index 0000000..c1f3133 --- /dev/null +++ b/app/Helpers/StatusHelper.php @@ -0,0 +1,36 @@ +update([ + 'status_transaksi' => $statusTransaksi, + ]); + + // UPDATE PEMESANAN + if ($transaksi->pemesanan) { + + $statusPemesanan = 'Proses'; + + if ( + $statusTransaksi == 'berhasil' || + $statusTransaksi == 'selesai' + ) { + $statusPemesanan = 'Selesai'; + } + + elseif ($statusTransaksi == 'ditolak') { + $statusPemesanan = 'Ditolak'; + } + + $transaksi->pemesanan->update([ + 'status' => $statusPemesanan, + 'tahap_saat_ini' => $tahap, + ]); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/GoogleController.php b/app/Http/Controllers/Auth/GoogleController.php new file mode 100644 index 0000000..5de9c17 --- /dev/null +++ b/app/Http/Controllers/Auth/GoogleController.php @@ -0,0 +1,52 @@ +redirect(); + } + + // 2. Callback dari Google + public function callback() + { + $googleUser = Socialite::driver('google')->user(); + + // cari user + $user = User::where('email', $googleUser->email)->first(); + + // kalau belum ada, buat baru + if (!$user) { + $user = User::create([ + 'name' => $googleUser->name, + 'email' => $googleUser->email, + 'password' => bcrypt(Str::random(16)), + 'google_avatar' => $googleUser->avatar, + 'profile_photo' => null, + ]); + } else { + // update avatar setiap login Google + $user->update([ + 'name' => $googleUser->name, + 'google_avatar' => $googleUser->avatar, + ]); + } + + // 🔥 INI PENTING: login ke session Laravel + Auth::login($user); + + // regenerate session biar navbar update + session()->regenerate(); + + return redirect('/'); // atau route home kamu + } +} \ No newline at end of file diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 5799e2b..575ab63 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -5,47 +5,45 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; +use Laravel\Socialite\Facades\Socialite; use App\Models\User; +use App\Models\LogAktivitas; class AuthController extends Controller { - // Tampilkan form login + // ========================= + // HALAMAN LOGIN + // ========================= public function index() { return view('login'); } - // Proses login - // AuthController.php - + // ========================= + // LOGIN MANUAL + // ========================= public function login(Request $request) { $request->validate([ - 'email' => 'required|email', + 'email' => 'required|email', 'password' => 'required', - ], [ - 'email.required' => 'Email wajib diisi', - 'email.email' => 'Format email tidak valid', - 'password.required' => 'Password wajib diisi', ]); $user = User::where('email_user', $request->email)->first(); - // AuthController.php - method login() - if ($user && Hash::check($request->password, $user->password_user)) { - + Auth::login($user); $request->session()->regenerate(); - // 🔥 AMBIL PARAMETER 'redirect' (pakai input() lebih reliable) - $redirectUrl = $request->input('redirect'); + // LOG AKTIVITAS LOGIN + LogAktivitas::create([ + 'id_user' => $user->id_user, + 'aktivitas' => 'Login ke sistem', + 'icon' => 'fa-right-to-bracket', + 'tipe' => 'login' + ]); - if ($redirectUrl && str_starts_with($redirectUrl, '/')) { - return redirect($redirectUrl); - } - - // Fallback jika redirect tidak valid / tidak ada if ($user->role_user === 'admin') { return redirect()->route('admin.welcome'); } @@ -54,92 +52,156 @@ public function login(Request $request) } return back()->withErrors([ - 'email' => 'Email atau password yang Anda masukkan salah.', + 'email' => 'Email atau password salah.' ])->withInput($request->except('password')); } - // Tampilkan form ganti password + // ========================= + // GOOGLE LOGIN + // ========================= + public function redirectToGoogle() + { + return Socialite::driver('google')->redirect(); + } + + public function handleGoogleCallback() + { + try { + + $googleUser = Socialite::driver('google')->user(); + + $user = User::where('email_user', $googleUser->email)->first(); + + if (!$user) { + + $user = User::create([ + 'nama_user' => $googleUser->name, + 'email_user' => $googleUser->email, + 'password_user' => bcrypt('google-login'), + 'role_user' => 'customer', + 'google_id' => $googleUser->id, + 'google_avatar' => $googleUser->avatar, + ]); + + } else { + + $user->update([ + 'google_id' => $googleUser->id, + 'google_avatar' => $googleUser->avatar, + ]); + } + + Auth::login($user); + request()->session()->regenerate(); + + // LOG AKTIVITAS GOOGLE LOGIN + LogAktivitas::create([ + 'id_user' => $user->id_user, + 'aktivitas' => 'Login menggunakan Google', + 'icon' => 'fa-google', + 'tipe' => 'login' + ]); + + return redirect()->route('halaman-katalog'); + + } catch (\Exception $e) { + + return redirect()->route('login')->with('error', 'Login Google gagal.'); + } + } + + // ========================= + // FORM GANTI PASSWORD + // ========================= public function formGantiPassword(Request $request) { $email = $request->query('email'); + return view('gantipass', compact('email')); } - // Proses update password + // ========================= + // UPDATE PASSWORD + // ========================= public function updatePassword(Request $request) { $request->validate([ - 'email' => 'required|email|exists:users,email_user', - 'password_baru' => 'required|min:8|confirmed', - ], [ - 'email.exists' => 'Email tidak terdaftar dalam sistem.', - 'password_baru.min' => 'Password minimal 8 karakter.', - 'password_baru.confirmed' => 'Konfirmasi password tidak cocok.', + 'email' => 'required|email|exists:user,email_user', + 'password_baru' => 'required|min:8|confirmed', ]); $user = User::where('email_user', $request->email)->first(); if ($user) { + $user->update([ 'password_user' => Hash::make($request->password_baru) ]); } - return redirect()->route('login') - ->with('success', 'Password berhasil diubah! Silakan login dengan password baru.'); + return redirect()->route('login')->with('success', 'Password berhasil diubah.'); } - // Logout + // ========================= + // LOGOUT + // ========================= public function logout(Request $request) { - $role = Auth::user()->role_user; // ← cek role sebelum logout - + $user = Auth::user(); + + // LOG SEBELUM LOGOUT + if ($user) { + LogAktivitas::create([ + 'id_user' => $user->id_user, + 'aktivitas' => 'Logout dari sistem', + 'icon' => 'fa-right-from-bracket', + 'tipe' => 'logout' + ]); + } + + $role = $user->role_user ?? null; + Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); - - // Redirect berdasarkan role + if ($role === 'admin') { - return redirect()->route('login'); // ← admin ke login + return redirect()->route('login'); } - - return redirect()->route('welcome'); // ← pengguna ke welcome + + return redirect()->route('welcome'); } + // ========================= // REGISTER + // ========================= public function register(Request $request) { - // 🔥 1. VALIDASI INPUT - $validated = $request->validate([ - 'nama_lengkap' => 'required|string|max:255', - 'email_user' => 'required|email|unique:users,email_user', - 'no_hp' => 'required|string|max:20', - 'pekerjaan' => 'nullable|string|max:100', + $request->validate([ + 'nama_user' => 'required', + 'email_user' => 'required|email|unique:user,email_user', + 'no_hp' => 'required', 'password_user' => 'required|min:8|confirmed', - ], [ - // Custom error messages (opsional tapi disarankan) - 'nama_lengkap.required' => 'Nama lengkap wajib diisi', - 'email_user.required' => 'Email wajib diisi', - 'email_user.email' => 'Format email tidak valid', - 'email_user.unique' => 'Email sudah terdaftar', - 'no_hp.required' => 'Nomor HP wajib diisi', - 'password_user.required' => 'Password wajib diisi', - 'password_user.min' => 'Password minimal 8 karakter', - 'password_user.confirmed' => 'Konfirmasi password tidak cocok', ]); - // 🔥 2. SIMPAN USER KE DATABASE + $role = ($request->email_user === 'admin@gmail.com') ? 'admin' : 'customer'; + $user = User::create([ - 'nama_user' => $validated['nama_lengkap'], - 'email_user' => $validated['email_user'], - 'no_hp' => $validated['no_hp'], - 'pekerjaan' => $validated['pekerjaan'] ?? null, - 'password_user' => Hash::make($validated['password_user']), - 'role_user' => 'user', // default role + 'nama_user' => $request->nama_user, + 'email_user' => $request->email_user, + 'no_hp' => $request->no_hp, + 'password_user' => bcrypt($request->password_user), + 'role_user' => $role, ]); - // 🔥 3. REDIRECT KE WELCOME DENGAN PESAN SUKSES - return redirect()->route('welcome') - ->with('success', 'Akun berhasil dibuat! Silakan login.'); + // LOG REGISTER + LogAktivitas::create([ + 'id_user' => $user->id_user, + 'aktivitas' => 'Mendaftar akun baru', + 'icon' => 'fa-user-plus', + 'tipe' => 'register' + ]); + + return redirect()->route('login')->with('success', 'Register berhasil.'); } } \ No newline at end of file diff --git a/app/Http/Controllers/BlokController.php b/app/Http/Controllers/BlokController.php new file mode 100644 index 0000000..b68fcf5 --- /dev/null +++ b/app/Http/Controllers/BlokController.php @@ -0,0 +1,51 @@ +get(); + + return view('admin.kelola-blok', compact('perumahan', 'blok')); + } + + public function store(Request $request, $id) + { + Blok::create([ + 'id_perumahan' => $id, + 'nama_blok' => $request->nama_blok, + 'jumlah_unit' => $request->jumlah_unit, + ]); + + return back()->with('success', 'Blok berhasil ditambahkan'); + } + + public function update(Request $request, $id) + { + $blok = Blok::findOrFail($id); + $blok->update([ + 'nama_blok' => $request->nama_blok, + ]); + + return back()->with('success', 'Blok berhasil diupdate'); + } + + public function destroy($id) + { + Blok::findOrFail($id)->delete(); + return back()->with('success', 'Blok berhasil dihapus'); + } + + // NGAMBIL DATA BLOK SESUAI DENGAN PERUMAHAN (DI HALAMAN TAMBAH RUMAH) + public function getByPerumahan($id) +{ + return Blok::where('id_perumahan', $id)->get(); +} +} \ No newline at end of file diff --git a/app/Http/Controllers/ChatbotController.php b/app/Http/Controllers/ChatbotController.php index 2765800..9398c22 100644 --- a/app/Http/Controllers/ChatbotController.php +++ b/app/Http/Controllers/ChatbotController.php @@ -3,69 +3,397 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Auth; +use App\Models\ChatSession; +use App\Models\ChatbotMessage; -class ChatBotController extends Controller +class ChatbotController extends Controller { - public function sendMessage(Request $request) + // ========================= + // HALAMAN CHATBOT + // ========================= + public function index() { - $message = strtolower(trim($request->input('message'))); - - // Database pengetahuan (bisa nanti dipindah ke database MySQL jika mau) - $responses = [ - [ - 'keywords' => ['halo', 'hai', 'hello', 'pagi', 'siang', 'malam'], - 'reply' => "Halo kak! 👋 Selamat datang di Carani Estate. Ada yang bisa dibantu hari ini?", - 'options' => ["Cari Rumah", "Info KPR", "Lokasi Kantor"] - ], - [ - 'keywords' => ['harga', 'biaya', 'mahal', 'murah', 'budget', 'dp'], - 'reply' => "Untuk harga, kami punya banyak pilihan mulai dari Rp 500 jutaan sampai miliaran, Kak. 😊 Kamu lagi cari properti di daerah mana nih?", - 'options' => ["Daerah Malang", "Daerah Batu", "Budget < 1 Milyar"] - ], - [ - 'keywords' => ['kpr', 'kredit', 'bank', 'cicil', 'bunga'], - 'reply' => "Siap! Untuk KPR, bunganya mulai dari 4% fixed tahun pertama lho. 🏦 Mau aku hitungin simulasi cicilannya sekalian?", - 'options' => ["Hitung Simulasi", "Syarat Dokumen"] - ], - [ - 'keywords' => ['lokasi', 'alamat', 'dimana', 'tempat', 'kantornya'], - 'reply' => "Kantor pemasaran kami ada di pusat kota, strategis banget! 📍 Mau dikirim lokasi Google Maps-nya?", - 'options' => ["Kirim Peta", "Jadwalkan Kunjungan"] - ], - [ - 'keywords' => ['tipe', 'jenis', 'rumah', 'apartemen', 'spesifikasi', 'luas'], - 'reply' => "Kami punya tipe Minimalis Modern dan Industrial Kekinian. Keduanya udah include kanopi & pagar loh! 🏠 Kamu lebih suka yang mana?", - 'options' => ["Lihat Foto Tipe A", "Lihat Foto Tipe B"] - ] - ]; - - $bestMatch = null; - $highestScore = 0; - - // Algoritma pencocokan sederhana (Naive Logic) - foreach ($responses as $data) { - $score = 0; - foreach ($data['keywords'] as $keyword) { - if (str_contains($message, $keyword)) { - $score++; - } + $chatSession = ChatSession::with([ + 'messages' => function ($q) { + $q->orderBy('created_at', 'asc'); } - if ($score > $highestScore) { - $highestScore = $score; - $bestMatch = $data; - } - } + ]) + ->where('id_user', auth()->id()) + ->where('status_chat', 'aktif') + ->first(); - if ($bestMatch && $highestScore > 0) { - return response()->json([ - 'reply' => $bestMatch['reply'], - 'options' => $bestMatch['options'] - ]); - } else { - return response()->json([ - 'reply' => "Waduh, maaf Kak, saya belum paham maksudnya 😅. Bisa coba tanya tentang 'Harga', 'KPR', atau 'Lokasi'?", - 'options' => ["Tanya Harga", "Tanya KPR"] - ]); - } + $messages = $chatSession + ? $chatSession->messages + : collect([]); + + return view( + 'halaman-chatbot', + compact( + 'chatSession', + 'messages' + ) + ); } + + + // ========================= + // KIRIM CHAT + // ========================= + public function kirim(Request $request) + { + $pesanUser = $request->input('message'); + + $userId = Auth::id(); + + // ========================= + // CEK SESSION CHAT + // ========================= + $chatSession = DB::table('chat_sessions') + ->where('id_user', $userId) + ->where('status_chat', 'aktif') + ->first(); + + // kalau belum ada session + if (!$chatSession) { + + $sessionId = + DB::table('chat_sessions') + ->insertGetId([ + 'id_user' => $userId, + 'status_chat' => 'aktif', + 'started_at' => now(), + 'ended_at' => now() + ]); + + } else { + + $sessionId = + $chatSession->id_sessions; + session([ + 'chat_session_id' => $sessionId + ]); + } + + + // ========================= + // SIMPAN CHAT USER + // ========================= + DB::table('chat_messages')->insert([ + 'id_sessions' => $sessionId, + 'sender' => 'user', + 'message' => $pesanUser, + 'created_at' => now() + ]); + + + // ========================= + // DEFAULT + // ========================= + $rekomendasiProperti = []; + + $balasan = 'Maaf kak, terjadi kesalahan 😥'; + + + // ========================= + // API KEY + // ========================= + $apiKey = env('GROQ_API_KEY'); + + if (!$apiKey) { + + return response()->json([ + + 'reply' => 'API key tidak ditemukan!', + + 'options' => [], + + 'properti' => [] + ]); + } + + + // ========================= + // DATA PROPERTI + // ========================= + $properti = DB::table('properti') + ->where('status_unit', 'tersedia') + ->select( + 'id_properti', + 'nama_properti', + 'jenis_properti', + 'kategori_properti', + 'tipe_properti', + 'harga_properti', + 'luas_bangunan', + 'luas_tanah', + 'stok_unit' + ) + ->get(); + + $propertiJson = + json_encode($properti); + + + // ========================= + // SYSTEM PROMPT + // ========================= + $systemPrompt = " +Kamu adalah CaraniBot, customer service virtual PT. Carani Bhanu Balakosa. + +Gaya bicara: +- Santai +- Natural +- Ramah +- Seperti admin chat WhatsApp +- Jangan terlalu formal +- Jangan terlalu kaku +- Gunakan emoji seperlunya 😊 +- Panggil user dengan 'kak' jika cocok + +ATURAN PENTING: +- DILARANG menggunakan markdown. +- DILARANG menggunakan tanda bintang seperti * atau **. +- DILARANG membuat bullet list. +- DILARANG membuat format poin-poin. +- Balasan harus seperti chat biasa. +- Gunakan kalimat natural dan mengalir. +- Jangan terlalu panjang. + +Jika user baru menyebut ingin cari rumah/properti: +- Jangan langsung memberi rekomendasi properti. +- Tanya kebutuhan user dulu secara natural. + +Contoh gaya yang BENAR: +'Siap kak 😊 +Boleh tahu budget dan lokasi yang kakak inginkan?' + +Contoh gaya yang SALAH: +'* Budget? * +* Lokasi? *' + +Kamu bisa membantu: +- konsultasi properti +- rekomendasi rumah +- KPR +- subsidi +- lokasi properti +- harga rumah +- proses pembelian +- pertanyaan umum pelanggan + +Data properti: +$propertiJson + +Jika ingin merekomendasikan properti, gunakan format: +[REKOMENDASI:id] + +Contoh: +[REKOMENDASI:1,2] +"; + + + try { + + $client = new \GuzzleHttp\Client(); + + $response = $client->post( + 'https://api.groq.com/openai/v1/chat/completions', + [ + + 'headers' => [ + + 'Content-Type' => 'application/json', + + 'Authorization' => 'Bearer ' . $apiKey + ], + + 'json' => [ + + 'model' => 'llama-3.1-8b-instant', + + 'max_tokens' => 500, + + 'messages' => [ + + [ + 'role' => 'system', + + 'content' => $systemPrompt + ], + + [ + 'role' => 'user', + + 'content' => $pesanUser + ] + ] + ] + ] + ); + + $result = + json_decode( + $response->getBody(), + true + ); + + $balasan = + $result['choices'][0]['message']['content'] + ?? 'Tidak ada balasan'; + + + // ========================= + // CEK TAG REKOMENDASI + // ========================= + if ( + preg_match( + '/\[REKOMENDASI:([\d,]+)\]/', + $balasan, + $matches + ) + ) { + + $ids = + explode(',', $matches[1]); + + $rekomendasiProperti = + DB::table('properti') + ->whereIn( + 'id_properti', + $ids + ) + ->where( + 'status_unit', + 'tersedia' + ) + ->select( + 'id_properti', + 'nama_properti', + 'jenis_properti', + 'kategori_properti', + 'tipe_properti', + 'harga_properti', + 'luas_bangunan', + 'luas_tanah', + 'stok_unit' + ) + ->get() + ->toArray(); + + // hapus tag dari chat + $balasan = trim( + preg_replace( + '/\[REKOMENDASI:[\d,]+\]/', + '', + $balasan + ) + ); + } + + } catch (\Exception $e) { + + $balasan = + 'Maaf kak, server lagi gangguan 😥'; + + $rekomendasiProperti = []; + } + + // ========================= + // SIMPAN BALASAN BOT + // ========================= + DB::table('chat_messages')->insert([ + + 'id_sessions' => $sessionId, + + 'sender' => 'bot', + + 'message' => $balasan, + + 'properti_data' => json_encode($rekomendasiProperti), + + 'created_at' => now() + ]); + + + // ========================= + // RESPONSE JSON + // ========================= + return response()->json([ + 'reply' => $balasan, + 'options' => [], + 'properti' => $rekomendasiProperti + ]); + } + + + // ========================= + // HAPUS RIWAYAT CHAT + // ========================= + public function hapusRiwayat() + { + $userId = auth()->id(); + + $session = DB::table('chat_sessions') + ->where('id_user', $userId) + ->where('status_chat', 'aktif') + ->first(); + + if ($session) { + + DB::table('chat_messages') + ->where('id_sessions', $session->id_sessions) + ->delete(); + + DB::table('chat_sessions') + ->where('id_sessions', $session->id_sessions) + ->delete(); + } + + return response()->json([ + 'success' => true + ]); + } + + // PESAN TERBARU + public function pesanTerbaru(Request $request) +{ + $sessionId = session('chat_session_id'); + + if (!$sessionId) { + + return response()->json([ + 'messages' => [] + ]); + } + + $lastId = $request->last_id ?? 0; + + $messages = \App\Models\ChatbotMessage::where( + 'id_sessions', + $sessionId + ) + ->where( + 'id_messages', + '>', + $lastId + ) + ->orderBy( + 'created_at', + 'asc' + ) + ->get(); + + \Log::info([ + 'session_chatbot' => $sessionId, + 'last_id' => $lastId, + 'jumlah_pesan' => $messages->count(), + 'messages' => $messages->pluck('sender') +]); + + return response()->json([ + 'messages' => $messages + ]); +} } \ No newline at end of file diff --git a/app/Http/Controllers/DokumenController.php b/app/Http/Controllers/DokumenController.php new file mode 100644 index 0000000..91493f4 --- /dev/null +++ b/app/Http/Controllers/DokumenController.php @@ -0,0 +1,196 @@ +validate([ + 'id_transaksi' => 'required|exists:transaksi,id_transaksi', + 'bukti_pembayaran' => 'required|image|mimes:jpg,jpeg,png,pdf|max:5120', + ]); + + // ambil transaksi + $transaksi = Transaksi::findOrFail($request->id_transaksi); + + // simpan file + $file = $request->file('bukti_pembayaran'); + + $namaFile = time() . '_' . $file->getClientOriginalName(); + + $path = $file->storeAs( + 'dokumen/bukti_pembayaran', + $namaFile, + 'public' + ); + + // ========================= + // 1. UPDATE TRANSAKSI (LEGACY SYSTEM tetap jalan) + // ========================= + $transaksi->bukti_transaksi = $namaFile; + $transaksi->status_transaksi = 'menunggu_verifikasi'; + $transaksi->save(); + + // ========================= + // 2. SIMPAN KE DOKUMEN (NEW SYSTEM untuk monitoring) + // ========================= + + Dokumen::create([ + 'id_user' => auth()->id(), + 'id_transaksi' => $transaksi->id_transaksi, + 'id_pemesanan' => $transaksi->pemesanan->id_pemesanan ?? null, + + 'jenis_dokumen' => 'bukti_pembayaran', + 'nama_file' => $namaFile, + 'path_file' => 'dokumen/bukti_pembayaran/'.$namaFile, + 'tipe_file' => $file->getClientMimeType(), + 'ukuran_file' => $file->getSize(), + 'status_verifikasi' => 'pending', + 'sumber_dokumen' => 'pelanggan', + ]); + + // ========================= + // LOG AKTIVITAS + // ========================= + LogAktivitas::create([ + 'id_user' => auth()->id(), + 'aktivitas' => 'Upload bukti pembayaran transaksi #' . $transaksi->id_transaksi, + 'icon' => 'fa-receipt', + 'tipe' => 'transaksi' + ]); + + return back()->with('success', 'Bukti pembayaran berhasil dikirim dan menunggu verifikasi admin.'); + } + + + /** + * ====================================== + * ADMIN - TERIMA BUKTI + * ====================================== + */ + public function approveBukti($id) +{ + $dokumen = Dokumen::findOrFail($id); + + // 1. update dokumen + $dokumen->update([ + 'status_verifikasi' => 'diterima', + 'verified_at' => now() + ]); + + // 2. ambil transaksi + pemesanan + $transaksi = Transaksi::with('pemesanan') + ->find($dokumen->id_transaksi); + + if ($transaksi) { + + // 3. UPDATE STATUS PAKAI SYNC (INI INTI NYA) + StatusHelper::sync( + $transaksi, + 'berhasil', + 'Pelunasan Pembayaran' + ); + + // 4. NOTIFIKASI (JANGAN DIHAPUS) + if ($transaksi->pemesanan) { + + $pemesanan = $transaksi->pemesanan; + + \App\Models\Notifikasi::create([ + 'id_user' => $pemesanan->id_user, + 'judul' => 'Transaksi Berhasil Diselesaikan', + 'pesan' => 'Pembayaran telah diverifikasi. Invoice sekarang tersedia.', + 'tipe' => 'pelunasan', + 'status_baca' => 0, + 'status_kirim' => 'terkirim', + 'channel' => 'in_app', + 'referensi_id' => $transaksi->id_transaksi, + 'referensi_tipe' => 'invoice', + ]); + } + } + + // 5. LOG AKTIVITAS (JUGA TETAP) + LogAktivitas::create([ + 'id_user' => auth()->id(), + 'aktivitas' => 'Menerima bukti pembayaran', + 'icon' => 'fa-check-circle', + 'tipe' => 'transaksi' + ]); + + return back()->with( + 'success', + 'Bukti pembayaran diterima dan transaksi selesai.' + ); +} + + + /** + * ====================================== + * ADMIN - TOLAK BUKTI + * ====================================== + */ + public function rejectBukti($id) +{ + $dokumen = Dokumen::findOrFail($id); + + // 1. update dokumen + $dokumen->update([ + 'status_verifikasi' => 'ditolak', + 'verified_at' => now() + ]); + + $transaksi = Transaksi::with('pemesanan') + ->find($dokumen->id_transaksi); + + if ($transaksi) { + + // 2. sync status (JANGAN MANUAL) + StatusHelper::sync( + $transaksi, + 'perlu_upload_ulang', // atau 'menunggu_verifikasi' + 'Upload Ulang Dokumen' + ); + + // 3. NOTIFIKASI (TETAP ADA) + if ($transaksi->pemesanan) { + + \App\Models\Notifikasi::create([ + 'id_user' => $transaksi->pemesanan->id_user, + 'judul' => 'Bukti Pembayaran Ditolak', + 'pesan' => 'Bukti pembayaran Anda ditolak. Silakan upload ulang dengan data yang benar.', + 'tipe' => 'verifikasi', + 'status_baca' => 0, + 'status_kirim' => 'terkirim', + 'channel' => 'in_app', + 'referensi_id' => $transaksi->id_transaksi, + 'referensi_tipe' => 'transaksi', + ]); + } + } + + // 4. LOG AKTIVITAS (TETAP) + LogAktivitas::create([ + 'id_user' => auth()->id(), + 'aktivitas' => 'Menolak bukti pembayaran', + 'icon' => 'fa-times-circle', + 'tipe' => 'transaksi' + ]); + + return back()->with('success', 'Bukti pembayaran ditolak.'); +} +} \ No newline at end of file diff --git a/app/Http/Controllers/LaporanPenjualanController.php b/app/Http/Controllers/LaporanPenjualanController.php new file mode 100644 index 0000000..394eb02 --- /dev/null +++ b/app/Http/Controllers/LaporanPenjualanController.php @@ -0,0 +1,564 @@ +date_start; + $dateEnd = $request->date_end; + + if ($dateStart && $dateEnd) { + + $query->whereBetween('tanggal_transaksi', [ + $dateStart . ' 00:00:00', + $dateEnd . ' 23:59:59' + ]); + + } + + + + /* + |-------------------------------------------------------------------------- + | FILTER STATUS + |-------------------------------------------------------------------------- + */ + + if ( + $request->filled('status_transaksi') && + $request->status_transaksi != 'all' + ) { + + $query->where( + 'status_transaksi', + $request->status_transaksi + ); + } + + + /* + |-------------------------------------------------------------------------- + | FILTER TIPE PROPERTI + |-------------------------------------------------------------------------- + */ + + if ( + $request->filled('type') && + $request->type != 'all' + ) { + + $query->whereHas('properti', function ($q) use ($request) { + + $q->where( + 'tipe_properti', + $request->type + ); + + }); + } + + + /* + |-------------------------------------------------------------------------- + | SEARCH + |-------------------------------------------------------------------------- + */ + + if ($request->filled('search')) { + + $search = $request->search; + + $query->where(function ($q) use ($search) { + + $q->where( + 'id_transaksi', + 'like', + "%{$search}%" + ) + + ->orWhereHas('user', function ($user) use ($search) { + + $user->where( + 'nama_user', + 'like', + "%{$search}%" + ); + + }) + + ->orWhereHas('properti', function ($properti) use ($search) { + + $properti->where( + 'nama_properti', + 'like', + "%{$search}%" + ); + + }); + + }); + } + + + /* + |-------------------------------------------------------------------------- + | DATA TRANSAKSI + |-------------------------------------------------------------------------- + */ + + $transaksi = $query + ->orderBy('tanggal_transaksi', 'desc') + ->paginate(10); + + + /* + |-------------------------------------------------------------------------- + | CLONE QUERY + |-------------------------------------------------------------------------- + */ + + $summaryQuery = clone $query; + + + /* + |-------------------------------------------------------------------------- + | TOTAL PENDAPATAN + |-------------------------------------------------------------------------- + */ + + $pendapatanBulanIni = (clone $summaryQuery) + ->where('status_transaksi', 'berhasil') + ->sum('total_harga'); + + + /* + |-------------------------------------------------------------------------- + | TOTAL TRANSAKSI + |-------------------------------------------------------------------------- + */ + + $totalTransaksi = (clone $summaryQuery) + ->count(); + + + /* + |-------------------------------------------------------------------------- + | TOTAL PENDING + |-------------------------------------------------------------------------- + */ + + $totalPending = (clone $summaryQuery) + + ->whereIn('status_transaksi', [ + + 'menunggu_verifikasi', + 'menunggu_pembayaran', + 'perlu_upload_ulang' + + ]) + + ->count(); + + + /* + |-------------------------------------------------------------------------- + | TOTAL DITOLAK + |-------------------------------------------------------------------------- + */ + + $totalBatal = (clone $summaryQuery) + + ->where('status_transaksi', 'ditolak') + + ->count(); + + + /* + |-------------------------------------------------------------------------- + | PERSENTASE DUMMY + |-------------------------------------------------------------------------- + */ + + $persenPendapatan = 12.5; + $persenTransaksi = 8.3; + $persenPending = -3.1; + $persenBatal = 1.2; + + + /* + |-------------------------------------------------------------------------- + | CHART BULANAN + |-------------------------------------------------------------------------- + */ + + $labelBulanan = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des' + ]; + + $dataTransaksi = []; + $dataPendapatan = []; + + for ($bulan = 1; $bulan <= 12; $bulan++) { + + // Total transaksi + $totalTransaksiBulanan = Transaksi::whereMonth('tanggal_transaksi', $bulan) + ->whereYear('tanggal_transaksi', date('Y')) + ->count(); + + // Total pendapatan + $totalPendapatanBulanan = Transaksi::whereMonth('tanggal_transaksi', $bulan) + ->whereYear('tanggal_transaksi', date('Y')) + ->where('status_transaksi', 'berhasil') + ->sum('total_harga'); + + $dataTransaksi[] = $totalTransaksiBulanan; + $dataPendapatan[] = $totalPendapatanBulanan; + } + + + /* + |-------------------------------------------------------------------------- + | CHART TAHUNAN + |-------------------------------------------------------------------------- + */ + + $labelTahunan = []; + $dataTahunanTransaksi = []; + $dataTahunanPendapatan = []; + + $tahunSekarang = date('Y'); + for ($tahun = $tahunSekarang - 4; $tahun <= $tahunSekarang; $tahun++) { + $labelTahunan[] = $tahun; + + // Total transaksi tahunan + $totalTransaksiTahunan = Transaksi::whereYear('tanggal_transaksi', $tahun) + ->count(); + + // Total pendapatan tahunan + $totalPendapatanTahunan = Transaksi::whereYear('tanggal_transaksi', $tahun) + ->where('status_transaksi', 'berhasil') + ->sum('total_harga'); + + $dataTahunanTransaksi[] = $totalTransaksiTahunan; + + $dataTahunanPendapatan[] = $totalPendapatanTahunan; + } + + + /* + |-------------------------------------------------------------------------- + | CHART MINGGUAN + |-------------------------------------------------------------------------- + */ + + $chartMingguan = Transaksi::selectRaw(' + WEEK(tanggal_transaksi) as minggu, + COUNT(*) as total + ') + ->whereMonth( + 'tanggal_transaksi', + now()->month + ) + ->whereYear( + 'tanggal_transaksi', + now()->year + ) + ->groupBy('minggu') + ->orderBy('minggu') + ->get(); + + + $labelMingguan = []; + $dataMingguan = []; + + + foreach ($chartMingguan as $item) { + + $labelMingguan[] = + 'Minggu ' . $item->minggu; + + $dataMingguan[] = + $item->total; + } + + + /* + |-------------------------------------------------------------------------- + | CHART STATUS + |-------------------------------------------------------------------------- + */ + + $chartStatus = [ + + 'berhasil' => Transaksi::where( + 'status_transaksi', + 'berhasil' + )->count(), + + 'menunggu_pembayaran' => Transaksi::where( + 'status_transaksi', + 'menunggu_pembayaran' + )->count(), + + 'menunggu_verifikasi' => Transaksi::where( + 'status_transaksi', + 'menunggu_verifikasi' + )->count(), + + 'perlu_upload_ulang' => Transaksi::where( + 'status_transaksi', + 'perlu_upload_ulang' + )->count(), + + 'ditolak' => Transaksi::where( + 'status_transaksi', + 'ditolak' + )->count(), + ]; + + + /* + |-------------------------------------------------------------------------- + | RETURN VIEW + |-------------------------------------------------------------------------- + */ + + return view( + 'admin.laporan_penjualan', + compact( + + 'transaksi', + + 'pendapatanBulanIni', + 'persenPendapatan', + + 'totalTransaksi', + 'persenTransaksi', + + 'totalPending', + 'persenPending', + + 'totalBatal', + 'persenBatal', + + // 'chartPenjualan', + 'chartStatus', + + 'labelBulanan', + 'dataTransaksi', + 'dataPendapatan', + + 'labelTahunan', + 'dataTahunanTransaksi', + 'dataTahunanPendapatan', + + 'labelMingguan', + 'dataMingguan' + ) + ); + } + + + /* + |-------------------------------------------------------------------------- + | EXPORT CSV + |-------------------------------------------------------------------------- + */ + + public function export(Request $request) + { + $query = Transaksi::with([ + 'user', + 'properti' + ]); + + + /* + |-------------------------------------------------------------------------- + | FILTER TANGGAL + |-------------------------------------------------------------------------- + */ + + if ( + $request->filled('date_start') && + $request->filled('date_end') + ) { + + $query->whereBetween( + 'tanggal_transaksi', + [ + $request->date_start . ' 00:00:00', + $request->date_end . ' 23:59:59' + ] + ); + } + + + /* + |-------------------------------------------------------------------------- + | FILTER STATUS + |-------------------------------------------------------------------------- + */ + + if ( + $request->filled('status_transaksi') && + $request->status_transaksi != 'all' + ) { + + $query->where( + 'status_transaksi', + $request->status_transaksi + ); + } + + + /* + |-------------------------------------------------------------------------- + | FILTER TIPE PROPERTI + |-------------------------------------------------------------------------- + */ + + if ( + $request->filled('type') && + $request->type != 'all' + ) { + + $query->whereHas( + 'properti', + function ($q) use ($request) { + + $q->where( + 'tipe_properti', + $request->type + ); + + } + ); + } + + + /* + |-------------------------------------------------------------------------- + | DATA EXPORT + |-------------------------------------------------------------------------- + */ + + $transaksi = $query + ->orderBy( + 'tanggal_transaksi', + 'desc' + ) + ->get(); + + + $filename = + 'laporan_penjualan_' . + now()->format('Ymd_His') . + '.csv'; + + + $headers = [ + + 'Content-Type' => + 'text/csv', + + 'Content-Disposition' => + "attachment; filename=$filename", + + ]; + + + $callback = function () use ($transaksi) { + + $file = fopen( + 'php://output', + 'w' + ); + + + /* + |-------------------------------------------------------------------------- + | HEADER CSV + |-------------------------------------------------------------------------- + */ + + fputcsv($file, [ + + 'ID Transaksi', + 'Tanggal', + 'Pembeli', + 'Properti', + 'Metode Pembayaran', + 'Total Harga', + 'Status' + + ]); + + + /* + |-------------------------------------------------------------------------- + | DATA CSV + |-------------------------------------------------------------------------- + */ + + foreach ($transaksi as $item) { + + fputcsv($file, [ + $item->id_transaksi, + $item->tanggal_transaksi, + $item->user->nama_user ?? '-', + $item->properti->nama_properti ?? '-', + $item->metode_pembayaran ?? '-', + $item->total_harga, + $item->status_transaksi + ]); + } + + fclose($file); + }; + + + return response()->stream( + $callback, + 200, + $headers + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/MonitoringController.php b/app/Http/Controllers/MonitoringController.php new file mode 100644 index 0000000..1910ceb --- /dev/null +++ b/app/Http/Controllers/MonitoringController.php @@ -0,0 +1,362 @@ +search) { + + $query->whereHas('user', function ($q) use ($request) { + + $q->where( + 'nama_user', + 'like', + '%' . $request->search . '%' + ); + + })->orWhereHas('properti', function ($q) use ($request) { + + $q->where( + 'nama_properti', + 'like', + '%' . $request->search . '%' + ); + + }); + } + + // FILTER STATUS + if ($request->status) { + + $query->where( + 'status', + $request->status + ); + } + + // FILTER METODE + if ($request->metode) { + + $query->where( + 'metode_pembayaran', + $request->metode + ); + } + + // SORTING + if ($request->sort == 'oldest') { + + $query->oldest(); + + } elseif ($request->sort == 'name_asc') { + + $query->join( + 'users', + 'pemesanan.id_user', + '=', + 'users.id_user' + )->orderBy( + 'users.nama_user', + 'asc' + ); + + } elseif ($request->sort == 'name_desc') { + + $query->join( + 'users', + 'pemesanan.id_user', + '=', + 'users.id_user' + )->orderBy( + 'users.nama_user', + 'desc' + ); + + } else { + + $query->latest(); + } + + $pemesanan = $query + ->latest() + ->paginate(6) + ->withQueryString(); + + return view( + 'admin.monitoring-pemesanan', + compact('pemesanan') + ); + } + + // ========================================================= + // DETAIL MONITORING ADMIN + // ========================================================= + public function show($id) + { + $pemesanan = Pemesanan::with([ + 'user', + 'properti', + 'dokumen' + ])->findOrFail($id); + + + // AMBIL RIWAYAT AKTIVITAS + $aktivitas = LogAktivitas::where( + 'ref_id', + $pemesanan->id_pemesanan + ) + ->latest() + ->get(); + + return view( + 'admin.detail-monitoring', + compact( + 'pemesanan', + 'aktivitas' + ) + ); + } + + // ========================================================= + // UPDATE MONITORING + // ========================================================= + public function update(Request $request, $id) + { + $pemesanan = Pemesanan::findOrFail($id); + + /* + |------------------------------------------------------------------ + | TENTUKAN STATUS TRANSAKSI + |------------------------------------------------------------------ + */ + + $statusTransaksi = 'menunggu_pembayaran'; + if (strtolower($request->status) == 'selesai') { + + $statusTransaksi = 'berhasil'; + + } + elseif (strtolower($request->status) == 'ditolak') { + + $statusTransaksi = 'ditolak'; + + } + elseif ( + strtolower($request->tahap_saat_ini) == 'pelunasan pembayaran' + ) { + + $statusTransaksi = 'menunggu_pembayaran'; + } + elseif ( + strtolower($request->tahap_saat_ini) == 'serah terima rumah' + ) { + + $statusTransaksi = 'berhasil'; + } + /* + |------------------------------------------------------------------ + | UPDATE DATA PEMESANAN + |------------------------------------------------------------------ + */ + + $pemesanan->update([ + + 'tahap_saat_ini' => + $request->tahap_saat_ini, + + 'status' => + $request->status, + + 'estimasi_proses' => + $request->estimasi_proses, + + 'catatan_admin' => + $request->catatan_admin, + ]); + + + /* + |------------------------------------------------------------------ + | UPDATE STATUS TRANSAKSI + |------------------------------------------------------------------ + */ + + $transaksi = Transaksi::where( + 'id_user', + $pemesanan->id_user + ) + ->where( + 'id_properti', + $pemesanan->id_properti + ) + ->orderBy('id_transaksi', 'desc') + ->first(); + + if ($transaksi) { + + $transaksi->update([ + + 'status_transaksi' => $statusTransaksi + ]); + } + + /* + |------------------------------------------------------------------ + | LOG AKTIVITAS + |------------------------------------------------------------------ + */ + + LogAktivitas::create([ + + 'id_user' => + auth()->user()->id_user, + + 'aktivitas' => + 'Admin mengubah progress menjadi "' . + $request->tahap_saat_ini . + '" dengan status "' . + $request->status . + '"', + + 'icon' => + 'fa-sync-alt', + + 'tipe' => + 'monitoring', + + 'ref_id' => + $pemesanan->id_pemesanan + ]); + + + /* + |------------------------------------------------------------------ + | KIRIM NOTIFIKASI + |------------------------------------------------------------------ + */ + + if ($request->kirim_notifikasi) { + $judul = 'Update Progress Pemesanan'; + $pesan = + 'Pemesanan properti kamu saat ini berada pada tahap "' . + $request->tahap_saat_ini . + '" dengan status "' . + $request->status . + '".'; + + // KHUSUS PELUNASAN + if ( + strtolower($request->progres) == 'pelunasan' || + strtolower($request->tahap_saat_ini) == 'pelunasan pembayaran' + ) { + $judul = 'Tagihan Pelunasan'; + $pesan = 'Silakan melakukan pelunasan pembayaran rumah Anda. Klik notifikasi ini untuk melihat invoice dan detail tagihan.'; + Notifikasi::create([ + 'id_user' => $pemesanan->id_user, + 'judul' => $judul, + 'pesan' => $pesan, + 'tipe' => 'pelunasan', + 'status_baca' => 0, + 'status_kirim' => 'terkirim', + 'channel' => 'in_app', + 'referensi_id' => $transaksi->id_transaksi, + 'referensi_tipe' => 'invoice', + ]); + + } else { + + Notifikasi::create([ + 'id_user' => $pemesanan->id_user, + 'judul' => $judul, + 'pesan' => $pesan, + 'tipe' => 'monitoring', + 'status_baca' => 0, + 'status_kirim' => 'terkirim', + 'channel' => 'in_app', + 'referensi_id' => $pemesanan->id_pemesanan, + 'referensi_tipe' => 'pemesanan', + ]); + } + + /* + |-------------------------------------------------------------- + | LOG NOTIFIKASI + |-------------------------------------------------------------- + */ + + LogAktivitas::create([ + + 'id_user' => + auth()->user()->id_user, + + 'aktivitas' => + 'Admin mengirim notifikasi monitoring pemesanan', + + 'icon' => + 'fa-paper-plane', + + 'tipe' => + 'monitoring', + + 'ref_id' => + $pemesanan->id_pemesanan + ]); + } + + + return redirect() + ->back() + ->with( + 'success', + 'Monitoring berhasil diperbarui' + ); + } + + + + // ========================================================= + // HALAMAN MONITORING PELANGGAN + // ========================================================= + public function pelanggan($id) +{ + // 1. Ambil transaksi dulu (dari klik user) + // $transaksi = Transaksi::with(['pemesanan','properti']) + // ->where('id_transaksi', $id) + // ->where('id_user', auth()->id()) + // ->firstOrFail(); + $pemesanan = Pemesanan::where('id_pemesanan', $id) + ->where('id_user', auth()->id()) + ->firstOrFail(); + + // 2. Ambil pemesanan dari transaksi + $transaksi = $pemesanan->transaksi; + + // 3. Ambil aktivitas dari PEMESANAN (bukan transaksi) + $aktivitas = LogAktivitas::where( + 'ref_id', + $pemesanan->id_pemesanan + ) + ->latest() + ->get(); + + return view('detail-pemesanan', compact('pemesanan', 'aktivitas', 'transaksi')); +} +} \ No newline at end of file diff --git a/app/Http/Controllers/PemesananController.php b/app/Http/Controllers/PemesananController.php index 7a5367b..f59ced6 100644 --- a/app/Http/Controllers/PemesananController.php +++ b/app/Http/Controllers/PemesananController.php @@ -3,121 +3,334 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Mail; + use App\Models\Transaksi; use App\Models\Properti; use App\Models\Notifikasi; use App\Models\Dokumen; use App\Models\User; -use Illuminate\Support\Facades\Auth; +use App\Models\Pemesanan; +use App\Models\LogAktivitas; + +use App\Mail\NotifikasiEmail; class PemesananController extends Controller { public function proses(Request $request) { + // ====================================== + // VALIDASI + // ====================================== + $request->validate([ - 'id_properti' => 'required|exists:properti,id_properti', - 'jenis_transaksi'=> 'required|in:lunas,kredit,kpr,cash,installment,other', + 'id_properti' => 'required|exists:properti,id_properti', + + 'jenis_transaksi' => 'required|in:lunas,kredit', + + 'bukti_booking' => $request->jenis_transaksi == 'lunas' + ? 'required|file|mimes:jpg,jpeg,png,pdf|max:5120' + : 'nullable' ]); - $properti = Properti::find($request->id_properti); + // ====================================== + // AMBIL DATA + // ====================================== + + $user = Auth::user(); + + $properti = Properti::findOrFail($request->id_properti); + + // ====================================== + // SIMPAN TRANSAKSI + // ====================================== - // Simpan Transaksi $transaksi = Transaksi::create([ - 'id_user' => Auth::user()->id_user, - 'id_properti' => $request->id_properti, - 'tanggal_transaksi'=> now()->toDateString(), - 'total_harga' => $properti->harga_properti, - 'jenis_transaksi' => $request->jenis_transaksi, + 'id_user' => $user->id_user, + + 'id_properti' => $request->id_properti, + + 'tanggal_transaksi' => now()->toDateString(), + + 'total_harga' => $properti->harga_properti, + + 'jenis_transaksi' => $request->jenis_transaksi, + 'status_transaksi' => 'menunggu_verifikasi', ]); - // Daftar dokumen yang perlu disimpan - // key = nama input di form, value = enum jenis_dokumen di DB + // ====================================== + // SIMPAN PEMESANAN + // ====================================== + + $pemesanan = Pemesanan::create([ + + 'kode_pemesanan' => 'PSN-' . time(), + 'id_transaksi' => $transaksi->id_transaksi, + 'id_user' => $user->id_user, + 'id_properti' => $request->id_properti, + 'total_harga' => $properti->harga_properti, + 'metode_pembayaran' => $request->jenis_transaksi, + 'tahap_saat_ini' => 'Pemesanan Dibuat', + 'status' => 'Proses', + 'tanggal_pemesanan' => now(), + ]); + + // ====================================== + // LOG AKTIVITAS + // ====================================== + + LogAktivitas::create([ + 'id_user' => $user->id_user, + + 'aktivitas' => 'Melakukan pemesanan properti ' . $properti->nama_properti, + + 'icon' => 'fa-cart-shopping', + + 'tipe' => 'transaksi' + ]); + + // ====================================== + // LIST DOKUMEN + // ====================================== + $dokumenList = [ - 'ktp' => 'ktp', - 'kk' => 'kk', - 'surat_nikah' => 'surat_nikah', - 'npwp' => 'npwp', - 'slip_gaji' => 'slip_gaji', - 'rekening_koran' => 'rekening_koran', - 'foto_3x4' => 'foto_3x4', - 'surat_kerja' => 'surat_kerja', - 'selfie' => 'selfie', - 'foto_tempat_kerja' => 'foto_tempat_kerja', + + 'ktp' => 'ktp', + 'kk' => 'kk', + 'surat_nikah' => 'surat_nikah', + 'npwp' => 'npwp', + 'slip_gaji' => 'slip_gaji', + 'rekening_koran' => 'rekening_koran', + 'foto_3x4' => 'foto_3x4', + 'surat_kerja' => 'surat_kerja', + 'selfie' => 'selfie', + 'foto_tempat_kerja' => 'foto_tempat_kerja', + // Wiraswasta - 'ktpw' => 'ktp', - 'kkW' => 'kk', - 'surat_nikahW' => 'surat_nikah', - 'npwp_W' => 'npwp', - 'spt' => 'spt', - 'surat_ket_usaha' => 'surat_ket_usaha', - 'surat_penghasilan_usaha'=> 'surat_penghasilan_usaha', - 'pembukuan_usaha' => 'pembukuan_usaha', - 'rekening_koranW' => 'rekening_koran', - 'selfieW' => 'selfie', - 'foto_tempat_usaha' => 'foto_tempat_usaha', + 'spt_pajak' => 'spt_pajak', + 'surat_keterangan_usaha' => 'surat_keterangan_usaha', + 'surat_penghasilan_usaha' => 'surat_penghasilan_usaha', + 'pembukuan_usaha' => 'pembukuan_usaha', + 'foto_lokasi_usaha' => 'foto_lokasi_usaha', ]; + // ====================================== + // SIMPAN DOKUMEN + // ====================================== + foreach ($dokumenList as $inputName => $jenisDokumen) { + if ($request->hasFile($inputName)) { - $files = is_array($request->file($inputName)) - ? $request->file($inputName) + + $files = is_array($request->file($inputName)) + ? $request->file($inputName) : [$request->file($inputName)]; foreach ($files as $file) { - $namaFile = time() . '_' . $file->getClientOriginalName(); - $path = $file->storeAs('dokumen/' . Auth::user()->id_user, $namaFile, 'public'); + + $namaFile = + time() . '_' . + $inputName . '_' . + $file->getClientOriginalName(); + + $path = $file->storeAs( + 'dokumen/' . $user->id_user, + $namaFile, + 'public' + ); Dokumen::create([ - 'id_user' => Auth::user()->id_user, - 'jenis_dokumen' => $jenisDokumen, - 'nama_file' => $namaFile, - 'path_file' => $path, - 'tipe_file' => $file->getClientMimeType(), - 'ukuran_file' => $file->getSize(), - 'status_verifikasi'=> 'pending', - 'catatan_verifikasi'=> '', + + 'id_user' => $user->id_user, + + 'id_pemesanan' => $pemesanan->id_pemesanan, + + 'id_transaksi' => $transaksi->id_transaksi, + + 'jenis_dokumen' => $jenisDokumen, + + 'nama_file' => $namaFile, + + 'path_file' => $path, + + 'tipe_file' => $file->getClientMimeType(), + + 'ukuran_file' => $file->getSize(), + + 'status_verifikasi' => 'pending', + + 'catatan_verifikasi' => '', ]); } } } - // Notifikasi ke User - Notifikasi::create([ - 'id_user' => Auth::user()->id_user, - 'judul' => 'Pemesanan Berhasil Dikirim', - 'pesan' => 'Pemesanan kamu untuk ' . $properti->nama_properti . ' sedang menunggu verifikasi admin.', - 'tipe' => 'transaksi', - 'status_baca' => 0, - 'status_kirim' => 'terkirim', - 'channel' => 'in_app', - 'referensi_id' => $transaksi->id_transaksi, - 'referensi_tipe' => 'transaksi', - ]); + // ====================================== + // SIMPAN BUKTI BOOKING + // ====================================== - // Notifikasi ke semua Admin - $admins = User::where('role_user', 'admin')->get(); - foreach ($admins as $admin) { - Notifikasi::create([ - 'id_user' => $admin->id_user, - 'judul' => 'Pemesanan Baru Masuk', - 'pesan' => Auth::user()->nama_user . ' memesan ' . $properti->nama_properti . '.', - 'tipe' => 'transaksi', - 'status_baca' => 0, - 'status_kirim' => 'terkirim', - 'channel' => 'in_app', - 'referensi_id' => $transaksi->id_transaksi, - 'referensi_tipe' => 'transaksi', + if ($request->hasFile('bukti_booking')) { + + $file = $request->file('bukti_booking'); + + $namaFile = + time() . '_booking_' . + $file->getClientOriginalName(); + + $path = $file->storeAs( + 'dokumen/' . $user->id_user, + $namaFile, + 'public' + ); + + Dokumen::create([ + + 'id_user' => $user->id_user, + + 'id_pemesanan' => $pemesanan->id_pemesanan, + + 'id_transaksi' => $transaksi->id_transaksi, + + 'jenis_dokumen' => 'bukti_booking', + + 'nama_file' => $namaFile, + + 'path_file' => $path, + + 'tipe_file' => $file->getClientMimeType(), + + 'ukuran_file' => $file->getSize(), + + 'status_verifikasi' => 'pending', + + 'catatan_verifikasi' => '', + ]); + + // ====================================== + // LOG AKTIVITAS DOKUMEN + // ====================================== + + LogAktivitas::create([ + 'id_user' => $user->id_user, + + 'aktivitas' => 'Mengupload bukti booking', + + 'icon' => 'fa-file-upload', + + 'tipe' => 'dokumen' ]); } - return redirect()->route('pemesanan.terima-kasih', ['id' => $transaksi->id_transaksi]); - } + // ====================================== + // NOTIFIKASI CUSTOMER + // ====================================== - $request->validate([ - 'jenis_transaksi' => 'required', - 'bukti_booking' => $request->jenis_transaksi == 'lunas' - ? 'required|file|mimes:jpg,jpeg,png,pdf|max:5120' - : 'nullable' -]); + Notifikasi::create([ + + 'id_user' => $user->id_user, + + 'judul' => 'Pemesanan Berhasil Dikirim', + + 'pesan' => + 'Pemesanan kamu untuk ' . + $properti->nama_properti . + ' sedang menunggu verifikasi admin.', + + 'tipe' => 'transaksi', + + 'status_baca' => 0, + + 'status_kirim' => 'terkirim', + + 'channel' => 'in_app', + + 'referensi_id' => $transaksi->id_transaksi, + + 'referensi_tipe' => 'transaksi', + ]); + + // ====================================== + // EMAIL CUSTOMER + // ====================================== + + if ($user->email_user) { + + Mail::to($user->email_user)->send( + + new NotifikasiEmail([ + + 'judul' => 'Pemesanan Berhasil', + + 'pesan' => + 'Pemesanan properti ' . + $properti->nama_properti . + ' berhasil dikirim dan menunggu verifikasi admin.' + ]) + ); + } + + // ====================================== + // NOTIFIKASI ADMIN + // ====================================== + + $admins = User::where('role_user', 'admin')->get(); + + foreach ($admins as $admin) { + + Notifikasi::create([ + + 'id_user' => $admin->id_user, + + 'judul' => 'Pemesanan Baru Masuk', + + 'pesan' => + $user->nama_user . + ' memesan ' . + $properti->nama_properti . '.', + + 'tipe' => 'transaksi', + + 'status_baca' => 0, + + 'status_kirim' => 'terkirim', + + 'channel' => 'in_app', + + 'referensi_id' => $transaksi->id_transaksi, + + 'referensi_tipe' => 'transaksi', + ]); + + // EMAIL ADMIN + + if ($admin->email_user) { + + Mail::to($admin->email_user)->send( + + new NotifikasiEmail([ + + 'judul' => 'Pemesanan Baru', + + 'pesan' => + $user->nama_user . + ' melakukan pemesanan properti ' . + $properti->nama_properti + ]) + ); + } + } + + // ====================================== + // REDIRECT + // ====================================== + + return redirect()->route( + 'pemesanan.terima-kasih', + [ + 'id' => $transaksi->id_transaksi + ] + ); + } } \ No newline at end of file diff --git a/app/Http/Controllers/PerumahanController.php b/app/Http/Controllers/PerumahanController.php new file mode 100644 index 0000000..b0838dc --- /dev/null +++ b/app/Http/Controllers/PerumahanController.php @@ -0,0 +1,66 @@ +update([ + 'nama_perumahan' => $request->nama_perumahan, + 'lokasi_perumahan' => $request->lokasi_perumahan, + ]); + + return redirect()->route('admin.perumahan') + ->with('success', 'Data berhasil diupdate'); +} + +public function destroy($id) +{ + Perumahan::where('id_perumahan', $id)->delete(); + + return redirect()->back()->with('success', 'Data perumahan berhasil dihapus'); +} + +public function store(Request $request) +{ + $perumahan = new Perumahan(); + $perumahan->nama_perumahan = $request->nama_perumahan; + $perumahan->lokasi_perumahan = $request->lokasi_perumahan; + $perumahan->save(); + + return redirect()->route('admin.perumahan') + ->with('success', 'Data berhasil ditambahkan'); +} + +public function kelolaBlok($id) +{ + $perumahan = Perumahan::with('blok') + ->findOrFail($id); + + return view( + 'admin.kelola_blok', + compact('perumahan') + ); +} +} diff --git a/app/Http/Controllers/TransaksiController.php b/app/Http/Controllers/TransaksiController.php new file mode 100644 index 0000000..c82f1f6 --- /dev/null +++ b/app/Http/Controllers/TransaksiController.php @@ -0,0 +1,123 @@ +findOrFail($id); + + LogAktivitas::create([ + 'id_user' => auth()->id(), + 'aktivitas' => + 'Membuka invoice transaksi #' . + $transaksi->id_transaksi, + 'icon' => 'fa-file-invoice', + 'tipe' => 'transaksi' + ]); + + return view( + 'invoice', + compact('transaksi') + ); + } + + // ========================= +// DOWNLOAD PDF INVOICE +// ========================= +public function downloadPdf($id) +{ + $transaksi = Transaksi::with([ + 'properti.gambar', + 'properti.blok', + 'properti.perumahan', + 'user' + ])->findOrFail($id); + + $pdf = Pdf::loadView( + 'pdf.invoice', + compact('transaksi') + ); + + return $pdf->download( + 'Invoice-'.$transaksi->id_transaksi.'.pdf' + ); +} + + + // ========================= + // SIMPAN PEMBAYARAN LUNAS + // ========================= + public function storePembayaranLunas(Request $request) +{ + $request->validate([ + 'id_transaksi' => 'required', + 'tanggal_transfer' => 'required', + // 'bank_pengirim' => 'required', + 'bukti_pembayaran' => + 'required|mimes:jpg,jpeg,png,pdf|max:5120' + ]); + + $file = $request->file('bukti_pembayaran'); + + $namaFile = + time() . '_' . + $file->getClientOriginalName(); + + $file->move( + public_path('uploads/pembayaran-lunas'), + $namaFile + ); + + $transaksi = + Transaksi::findOrFail( + $request->id_transaksi + ); + + $transaksi->bukti_transaksi = + $namaFile; + + $transaksi->metode_pembayaran = + 'transfer_bank'; + + $transaksi->status_transaksi = + 'menunggu_verifikasi'; + + $transaksi->save(); + dd($transaksi->fresh()->bukti_transaksi); + + LogAktivitas::create([ + 'id_user' => auth()->id(), + 'aktivitas' => + 'Mengupload bukti pembayaran lunas untuk transaksi #' + . $transaksi->id_transaksi, + 'icon' => 'fa-file-invoice-dollar', + 'tipe' => 'transaksi' + ]); + + return redirect() + ->route( + 'invoice', + $transaksi->id_transaksi + ) + ->with( + 'success', + 'Bukti pembayaran berhasil dikirim dan sedang menunggu verifikasi admin.' + ); +} + + +} \ No newline at end of file diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index d7f2236..fb19183 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -1,13 +1,17 @@ delete(); + return redirect()->back()->with('success', 'User berhasil dihapus.'); } - // 🔥🔥🔥 TAMBAHAN UNTUK UPLOAD PP - public function uploadPP(Request $request) + + // ========================= + // UPLOAD PROFIL + // ========================= + public function updateProfile(Request $request) { $request->validate([ - 'foto' => 'required|image|mimes:jpg,jpeg,png|max:2048' + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:255' ]); - // 🔥 WAJIB pakai ini $user = Auth::user(); - if ($request->hasFile('foto')) { - $file = $request->file('foto'); - $namaFile = time().'_'.$file->getClientOriginalName(); - $file->move(public_path('uploads/profile'), $namaFile); - - $user->profile_photo = $namaFile; - } + $user->name = $request->name; + $user->email = $request->email; $user->save(); - return back()->with('success', 'Berhasil upload PP'); + return back()->with('success', 'Profil berhasil diperbarui.'); } + + // ========================= + // UPLOAD FOTO PROFIL + // ========================= + public function uploadPP(Request $request) +{ + $request->validate([ + 'photo' => 'required|image|mimes:jpg,jpeg,png|max:2048' + ]); + + $user = Auth::user(); + + if ($request->hasFile('photo')) { + + // hapus foto lama kalau ada + if ($user->profile_photo) { + Storage::disk('public')->delete( + 'profile_photos/' . $user->profile_photo + ); + } + + // upload foto baru + $file = $request->file('photo'); + + // simpan ke storage/app/public/profile_photos + $path = $file->store('profile_photos', 'public'); + + // simpan nama file ke database + $user->profile_photo = basename($path); + $user->save(); + } + + return back()->with('success', 'Foto profil berhasil diperbarui.'); +} } \ No newline at end of file diff --git a/app/Http/Controllers/VisitorController.php b/app/Http/Controllers/VisitorController.php index 3df5f3b..84440d3 100644 --- a/app/Http/Controllers/VisitorController.php +++ b/app/Http/Controllers/VisitorController.php @@ -2,9 +2,15 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; +use App\Models\VisitorLog; class VisitorController extends Controller { - // -} + public static function track($page) + { + VisitorLog::create([ + 'ip_address' => request()->ip(), + 'page' => $page + ]); + } +} \ No newline at end of file diff --git a/app/Mail/NotifikasiEmail.php b/app/Mail/NotifikasiEmail.php new file mode 100644 index 0000000..4f9514d --- /dev/null +++ b/app/Mail/NotifikasiEmail.php @@ -0,0 +1,23 @@ +data = $data; + } + + public function build() + { + return $this->subject($this->data['judul']) + ->view('emails.notifikasi') + ->with($this->data); + } +} \ No newline at end of file diff --git a/app/Models/Blok.php b/app/Models/Blok.php index 1fe99a3..91c2195 100644 --- a/app/Models/Blok.php +++ b/app/Models/Blok.php @@ -8,4 +8,21 @@ class Blok extends Model { protected $table = 'blok'; protected $primaryKey = 'id_blok'; + public $timestamps = false; + + protected $fillable = [ + 'id_perumahan', + 'nama_blok' + ]; + + + public function perumahan() +{ + return $this->belongsTo( + \App\Models\Perumahan::class, + 'id_perumahan', + 'id_perumahan' + ); } +} + diff --git a/app/Models/ChatSession.php b/app/Models/ChatSession.php new file mode 100644 index 0000000..3a54c13 --- /dev/null +++ b/app/Models/ChatSession.php @@ -0,0 +1,31 @@ +hasMany(ChatbotMessage::class, 'id_sessions', 'id_sessions'); + } + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } +} \ No newline at end of file diff --git a/app/Models/ChatbotMessage.php b/app/Models/ChatbotMessage.php new file mode 100644 index 0000000..afd29a7 --- /dev/null +++ b/app/Models/ChatbotMessage.php @@ -0,0 +1,31 @@ +belongsTo( + ChatSession::class, + 'id_sessions', + 'id_sessions' + ); + } +} \ No newline at end of file diff --git a/app/Models/Dokumen.php b/app/Models/Dokumen.php index 5860e2b..574bd49 100644 --- a/app/Models/Dokumen.php +++ b/app/Models/Dokumen.php @@ -7,17 +7,39 @@ class Dokumen extends Model { protected $table = 'dokumen'; + protected $primaryKey = 'id_dokumen'; + public $timestamps = false; protected $fillable = [ - 'id_user', 'jenis_dokumen', 'nama_file', - 'path_file', 'tipe_file', 'ukuran_file', - 'status_verifikasi', 'catatan_verifikasi', + 'id_user', + 'id_pemesanan', + 'id_transaksi', + 'jenis_dokumen', + 'nama_file', + 'path_file', + 'tipe_file', + 'ukuran_file', + 'status_verifikasi', + 'catatan_verifikasi', + 'sumber_dokumen', ]; + // ✅ Relasi ke Transaksi + public function transaksi() + { + return $this->belongsTo(\App\Models\Transaksi::class, 'id_transaksi', 'id_transaksi'); + } + + // ✅ Relasi ke User public function user() { return $this->belongsTo(User::class, 'id_user', 'id_user'); } -} + + public function pemesanan() + { + return $this->belongsTo(Pemesanan::class, 'id_pemesanan'); + } +} \ No newline at end of file diff --git a/app/Models/GambarProperti.php b/app/Models/GambarProperti.php new file mode 100644 index 0000000..92fd4ce --- /dev/null +++ b/app/Models/GambarProperti.php @@ -0,0 +1,22 @@ +belongsTo(Properti::class, 'id_properti', 'id_properti'); + } +} \ No newline at end of file diff --git a/app/Models/LeadEmail.php b/app/Models/LeadEmail.php new file mode 100644 index 0000000..fa3a6c8 --- /dev/null +++ b/app/Models/LeadEmail.php @@ -0,0 +1,13 @@ +belongsTo(User::class, 'id_user'); + } +} diff --git a/app/Models/Pemesanan.php b/app/Models/Pemesanan.php new file mode 100644 index 0000000..5c50dfc --- /dev/null +++ b/app/Models/Pemesanan.php @@ -0,0 +1,52 @@ +belongsTo(User::class, 'id_user'); + } + + public function properti() + { + return $this->belongsTo(Properti::class, 'id_properti'); + } + + public function dokumen() + { + return $this->hasMany( + Dokumen::class, + 'id_pemesanan', + 'id_pemesanan' + ); + } + + public function transaksi() + { + return $this->belongsTo( + Transaksi::class, + 'id_transaksi', + 'id_transaksi' + ); + } +} \ No newline at end of file diff --git a/app/Models/Perumahan.php b/app/Models/Perumahan.php index 89e0ee5..be162d3 100644 --- a/app/Models/Perumahan.php +++ b/app/Models/Perumahan.php @@ -1,12 +1,22 @@ hasMany(Blok::class, 'id_perumahan', 'id_perumahan'); + } } \ No newline at end of file diff --git a/app/Models/Properti.php b/app/Models/Properti.php index 06b2304..f612ca7 100644 --- a/app/Models/Properti.php +++ b/app/Models/Properti.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use App\Models\Perumahan; use App\Models\Blok; +use App\Models\GambarProperti; class Properti extends Model { @@ -28,8 +29,13 @@ class Properti extends Model 'status_unit', 'id_perumahan', 'id_blok', - 'deskripsi', // jika ada - // ... tambah kolom lainnya sesuai tabel + + 'bookingFee', + 'luas_bangunan', + 'luas_tanah', + 'stok_unit', + + 'deskripsi', ]; // ✅ Relationship ke Perumahan @@ -74,4 +80,10 @@ public function getEstimasiCicilanAttribute() return 'Rp ' . number_format($cicilan, 0, ',', '.'); } + + // RELASI KE GAMBAR PROPERTI + public function gambar() +{ + return $this->hasMany(GambarProperti::class, 'id_properti', 'id_properti'); +} } \ No newline at end of file diff --git a/app/Models/Transaksi.php b/app/Models/Transaksi.php index cdc2757..566c663 100644 --- a/app/Models/Transaksi.php +++ b/app/Models/Transaksi.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use App\Models\Kpr; +use App\Models\Dokumen; // ✅ Tambah import ini class Transaksi extends Model { @@ -17,7 +18,9 @@ class Transaksi extends Model 'tanggal_transaksi', 'total_harga', 'jenis_transaksi', + 'metode_pembayaran', 'status_transaksi', + 'bukti_transaksi' ]; // Relasi ke properti @@ -26,18 +29,43 @@ public function properti() return $this->belongsTo(Properti::class, 'id_properti', 'id_properti'); } + // Relasi ke user public function user() { return $this->belongsTo(User::class, 'id_user', 'id_user'); } + // Relasi ke pembayaran public function pembayaran() { return $this->hasMany(Pembayaran::class, 'id_transaksi', 'id_transaksi'); } + // Relasi ke KPR public function kpr() { return $this->hasOne(Kpr::class, 'id_transaksi', 'id_transaksi'); } + + // ✅ TAMBAHKAN INI: Relasi ke Dokumen + public function dokumen() + { + return $this->hasMany( + Dokumen::class, + 'id_transaksi', + 'id_transaksi' + ); + } + + // RELASI KE PEMESANAN + public function pemesanan() + { + return $this->hasOne( + Pemesanan::class, + 'id_transaksi', + 'id_transaksi' + ); + } + + } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 9dca9dd..b733f2f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,48 +10,43 @@ class User extends Authenticatable { use HasFactory, Notifiable; - // ✅ Tentukan primary key kustom + // Nama tabel & primary key custom protected $table = 'user'; protected $primaryKey = 'id_user'; - public $incrementing = true; // atau false jika id_user bukan auto-increment - protected $keyType = 'int'; // atau 'string' jika UUID - // ✅ Tentukan nama kolom password kustom - protected $password = 'password_user'; + public $incrementing = true; + protected $keyType = 'int'; - // ✅ Kolom yang bisa diisi mass-assignment + // Kolom yang boleh diisi protected $fillable = [ 'nama_user', 'email_user', 'no_hp', 'password_user', 'role_user', + 'google_id', + 'google_avatar', ]; - // ✅ Hidden attributes (tidak dikirim saat toJSON/toArray) + // Hidden fields protected $hidden = [ 'password_user', 'remember_token', ]; - // ✅ Beritahu Laravel field mana yang digunakan untuk auth - public function getAuthIdentifierName() - { - return 'id_user'; - } - + // Laravel auth pakai password_user public function getAuthPassword() { return $this->password_user; } - public function getAuthPasswordName() - { - return 'password_user'; - } - + // Relasi public function dokumen() { - return $this->hasMany(\App\Models\Dokumen::class, 'id_user', 'id_user'); + return $this->hasMany( + \App\Models\Dokumen::class, + 'id_user', + 'id_user' + ); } } \ No newline at end of file diff --git a/app/Models/VisitorLog.php b/app/Models/VisitorLog.php new file mode 100644 index 0000000..d8d824d --- /dev/null +++ b/app/Models/VisitorLog.php @@ -0,0 +1,13 @@ +=7.1||>=8.0" + }, + "require-dev": { + "ext-simplexml": "*", + "friendsofphp/php-cs-fixer": "^2.17", + "mockery/mockery": "^1.3.3", + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5||9.5" + }, + "suggest": { + "ext-simplexml": "For decoding XML-based responses." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev", + "dev-develop": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth1-client/issues", + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0" + }, + "time": "2024-12-10T19:59:05+00:00" + }, { "name": "league/uri", "version": "7.8.1", @@ -2083,6 +2527,73 @@ ], "time": "2026-03-08T20:05:35+00:00" }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", @@ -2685,6 +3196,125 @@ ], "time": "2026-03-17T20:53:51+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-http/discovery", "version": "1.20.0", @@ -2895,6 +3525,116 @@ ], "time": "2025-12-27T19:41:33+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.52", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce", + "reference": "2adaefc83df2ec548558307690f376dd7d4f4fce", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.52" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2026-04-27T07:02:15+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -3584,6 +4324,86 @@ }, "time": "2025-12-14T04:43:48+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", @@ -6085,6 +6905,149 @@ ], "time": "2026-02-15T10:53:20+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", diff --git a/config/app.php b/config/app.php index 423eed5..c74a68d 100644 --- a/config/app.php +++ b/config/app.php @@ -65,7 +65,7 @@ | */ - 'timezone' => 'UTC', + 'timezone' => 'Asia/Jakarta', /* |-------------------------------------------------------------------------- diff --git a/config/services.php b/config/services.php index 6a90eb8..45dc538 100644 --- a/config/services.php +++ b/config/services.php @@ -14,6 +14,12 @@ | */ + 'google' => [ + 'client_id' => env('GOOGLE_CLIENT_ID'), + 'client_secret' => env('GOOGLE_CLIENT_SECRET'), + 'redirect' => env('GOOGLE_REDIRECT_URI'), + ], + 'postmark' => [ 'key' => env('POSTMARK_API_KEY'), ], diff --git a/database/migrations/2026_05_03_110331_create_lead_emails_table.php b/database/migrations/2026_05_03_110331_create_lead_emails_table.php new file mode 100644 index 0000000..d7f0ef4 --- /dev/null +++ b/database/migrations/2026_05_03_110331_create_lead_emails_table.php @@ -0,0 +1,36 @@ +id(); + + $table->string('email')->unique(); + + $table->string('sumber')->nullable(); + + $table->string('ip_address')->nullable(); + + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('lead_emails'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_03_144621_create_visitor_logs__table.php b/database/migrations/2026_05_03_144621_create_visitor_logs__table.php new file mode 100644 index 0000000..fb62b20 --- /dev/null +++ b/database/migrations/2026_05_03_144621_create_visitor_logs__table.php @@ -0,0 +1,34 @@ +id(); + + $table->string('ip_address')->nullable(); + + $table->string('page')->nullable(); + + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('visitor_logs_'); + } +}; diff --git a/database/migrations/2026_05_05_172115_add_google_avatar_to_user_table.php b/database/migrations/2026_05_05_172115_add_google_avatar_to_user_table.php new file mode 100644 index 0000000..53405cf --- /dev/null +++ b/database/migrations/2026_05_05_172115_add_google_avatar_to_user_table.php @@ -0,0 +1,29 @@ +string('google_id')->nullable(); + $table->text('google_avatar')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('user', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2026_05_07_170900_add_id_transaksi_to_dokumen_table.php b/database/migrations/2026_05_07_170900_add_id_transaksi_to_dokumen_table.php new file mode 100644 index 0000000..a506ef1 --- /dev/null +++ b/database/migrations/2026_05_07_170900_add_id_transaksi_to_dokumen_table.php @@ -0,0 +1,23 @@ +unsignedBigInteger('id_transaksi') + ->after('id_user'); + }); + } + + public function down(): void + { + Schema::table('dokumen', function (Blueprint $table) { + $table->dropColumn('id_transaksi'); + }); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_14_201642_create_log_aktivitas_table.php b/database/migrations/2026_05_14_201642_create_log_aktivitas_table.php new file mode 100644 index 0000000..10875af --- /dev/null +++ b/database/migrations/2026_05_14_201642_create_log_aktivitas_table.php @@ -0,0 +1,43 @@ +id(); + + // HARUS SAMA: INT (bukan bigInteger) + $table->integer('id_user')->nullable(); + + $table->foreign('id_user') + ->references('id_user') + ->on('user') + ->onDelete('set null'); + + $table->string('aktivitas'); + $table->string('icon')->nullable(); + $table->string('tipe')->nullable(); + $table->unsignedBigInteger('ref_id')->nullable(); + + $table->timestamps(); + + $table->index('id_user'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('log_aktivitas'); + } +}; \ No newline at end of file diff --git a/database/migrations/2026_05_16_183814_add_properti_data_to_chat_messages.php b/database/migrations/2026_05_16_183814_add_properti_data_to_chat_messages.php new file mode 100644 index 0000000..ef97a04 --- /dev/null +++ b/database/migrations/2026_05_16_183814_add_properti_data_to_chat_messages.php @@ -0,0 +1,32 @@ +json('properti_data') + ->nullable() + ->after('message'); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('chat_messages', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2026_05_25_161821_create_gambar_properti_table.php b/database/migrations/2026_05_25_161821_create_gambar_properti_table.php new file mode 100644 index 0000000..0cbea8e --- /dev/null +++ b/database/migrations/2026_05_25_161821_create_gambar_properti_table.php @@ -0,0 +1,33 @@ +id('id_gambar'); + $table->unsignedBigInteger('id_properti'); + $table->string('path_gambar'); + $table->timestamps(); + $table->foreign('id_properti') + ->references('id_properti') + ->on('properti') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gambar_properti'); + } +}; diff --git a/public/images/cta.webp b/public/images/cta.webp new file mode 100644 index 0000000..621f7f5 Binary files /dev/null and b/public/images/cta.webp differ diff --git a/public/images/dapur 30.png b/public/images/dapur 30.png new file mode 100644 index 0000000..4afb563 Binary files /dev/null and b/public/images/dapur 30.png differ diff --git a/public/images/gambar-perusahaan.webp b/public/images/gambar-perusahaan.webp new file mode 100644 index 0000000..6b54afb Binary files /dev/null and b/public/images/gambar-perusahaan.webp differ diff --git a/public/images/header-sistem.png b/public/images/header-sistem.png new file mode 100644 index 0000000..1abf467 Binary files /dev/null and b/public/images/header-sistem.png differ diff --git a/public/images/header1.png b/public/images/header1.png new file mode 100644 index 0000000..2315a46 Binary files /dev/null and b/public/images/header1.png differ diff --git a/public/images/header1.webp b/public/images/header1.webp new file mode 100644 index 0000000..5adafa6 Binary files /dev/null and b/public/images/header1.webp differ diff --git a/public/images/kamar tidur 30.png b/public/images/kamar tidur 30.png new file mode 100644 index 0000000..0907e5c Binary files /dev/null and b/public/images/kamar tidur 30.png differ diff --git a/public/images/logoPT Pengguna.jpg b/public/images/logoPT Pengguna.jpg new file mode 100644 index 0000000..e29ff9a Binary files /dev/null and b/public/images/logoPT Pengguna.jpg differ diff --git a/public/images/placeholder-properti.png b/public/images/placeholder-properti.png new file mode 100644 index 0000000..f4d4b82 Binary files /dev/null and b/public/images/placeholder-properti.png differ diff --git a/public/images/ruang tamu 30.png b/public/images/ruang tamu 30.png new file mode 100644 index 0000000..a998cf6 Binary files /dev/null and b/public/images/ruang tamu 30.png differ diff --git a/public/images/rumah30.png b/public/images/rumah30.png new file mode 100644 index 0000000..a6c542a Binary files /dev/null and b/public/images/rumah30.png differ diff --git a/public/images/rumah60.png b/public/images/rumah60.png new file mode 100644 index 0000000..308de9b Binary files /dev/null and b/public/images/rumah60.png differ diff --git a/public/uploads/pembayaran-lunas/1779041095_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779041095_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779041095_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779041474_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779041474_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779041474_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779042174_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779042174_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779042174_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779042316_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779042316_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779042316_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779042782_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779042782_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779042782_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779042821_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779042821_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779042821_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779074366_Activity diagram form pemesanan.drawio.png b/public/uploads/pembayaran-lunas/1779074366_Activity diagram form pemesanan.drawio.png new file mode 100644 index 0000000..c699cda Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779074366_Activity diagram form pemesanan.drawio.png differ diff --git a/public/uploads/pembayaran-lunas/1779078559_Activity diagram login pelanggan.drawio.png b/public/uploads/pembayaran-lunas/1779078559_Activity diagram login pelanggan.drawio.png new file mode 100644 index 0000000..f17f016 Binary files /dev/null and b/public/uploads/pembayaran-lunas/1779078559_Activity diagram login pelanggan.drawio.png differ diff --git a/resources/views/admin/aktivitas.blade.php b/resources/views/admin/aktivitas.blade.php new file mode 100644 index 0000000..3c11b90 --- /dev/null +++ b/resources/views/admin/aktivitas.blade.php @@ -0,0 +1,1390 @@ + + +
+ + +Pantau seluruh aktivitas yang dilakukan oleh pengguna secara real-time
+