guard('admin')->user(); if ($admin) { if ($admin->role !== 'super_admin') { // Admin biasa hanya bisa lihat kontrakan milik mereka $query->where('admin_id', $admin->id); } // Super admin bisa lihat semua kontrakan } // ========== FILTER: SEARCH (Nama, Alamat, Fasilitas) ========== if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('nama', 'LIKE', "%{$search}%") ->orWhere('alamat', 'LIKE', "%{$search}%") ->orWhere('fasilitas', 'LIKE', "%{$search}%"); }); } // ========== FILTER: RANGE HARGA (Min & Max dengan Slider) ========== if ($request->filled('harga_min')) { $query->where('harga', '>=', $request->harga_min); } if ($request->filled('harga_max')) { $query->where('harga', '<=', $request->harga_max); } // ========== FILTER: JARAK MAKSIMAL (Slider dalam KM) ========== if ($request->filled('jarak_max')) { // Konversi km ke meter (karena jarak di DB dalam meter) $jarakMeter = $request->jarak_max * 1000; $query->where('jarak', '<=', $jarakMeter); } // ========== FILTER: jumlah_kamar MINIMAL & MAKSIMAL ========== if ($request->filled('jumlah_kamar_min')) { $query->where('jumlah_kamar', '>=', $request->jumlah_kamar_min); } if ($request->filled('jumlah_kamar_max')) { $query->where('jumlah_kamar', '<=', $request->jumlah_kamar_max); } // ========== FILTER: FASILITAS (Multiple Checkbox) ========== if ($request->filled('fasilitas_filter')) { $fasilitasArray = $request->fasilitas_filter; foreach ($fasilitasArray as $fasilitas) { // Cari kontrakan yang fasilitasnya mengandung kata kunci ini $query->where('fasilitas', 'LIKE', "%{$fasilitas}%"); } } // ========== SORTING ========== $sortBy = $request->get('sort_by', 'terbaru'); // Default: terbaru switch ($sortBy) { case 'nama_asc': $query->orderBy('nama', 'ASC'); break; case 'nama_desc': $query->orderBy('nama', 'DESC'); break; case 'harga_termurah': $query->orderBy('harga', 'ASC'); break; case 'harga_termahal': $query->orderBy('harga', 'DESC'); break; case 'jarak_terdekat': $query->orderBy('jarak', 'ASC'); break; case 'jarak_terjauh': $query->orderBy('jarak', 'DESC'); break; case 'jumlah_kamar_terbesar': $query->orderBy('jumlah_kamar', 'DESC'); break; case 'jumlah_kamar_terkecil': $query->orderBy('jumlah_kamar', 'ASC'); break; case 'terlama': $query->orderBy('created_at', 'ASC'); break; case 'terbaru': default: $query->orderBy('created_at', 'DESC'); break; } // Get results dengan pagination $kontrakan = $query->paginate(12)->withQueryString(); // 12 items per halaman // ========== STATISTIK & DATA UNTUK FILTER UI ========== // Statistik hanya berdasarkan kontrakan milik admin yang sedang login $statQuery = Kontrakan::query(); if ($admin && $admin->role !== 'super_admin') { $statQuery->where('admin_id', $admin->id); } $totalKontrakan = $statQuery->count(); // Jumlah hasil setelah filter $filteredCount = $kontrakan->total(); $hasData = $totalKontrakan > 0; // Range harga untuk slider/statistik $hargaMin = $hasData ? ($statQuery->min('harga') ?? 0) : 0; $hargaMax = $hasData ? ($statQuery->max('harga') ?? 0) : 0; // Range jarak untuk slider/statistik $jarakMaxDb = $hasData ? ($statQuery->max('jarak') ?? 0) : 0; $jarakMaxKm = $jarakMaxDb > 0 ? ceil($jarakMaxDb / 1000) : 0; // Range jumlah kamar untuk statistik $jumlah_kamarMin = $hasData ? ($statQuery->min('jumlah_kamar') ?? 0) : 0; $jumlah_kamarMax = $hasData ? ($statQuery->max('jumlah_kamar') ?? 0) : 0; // Daftar fasilitas unik untuk checkbox (extract dari semua kontrakan) // Daftar fasilitas unik untuk checkbox (extract dari semua kontrakan) $allFasilitas = Kontrakan::pluck('fasilitas')->filter(); $fasilitasUnique = collect(); foreach ($allFasilitas as $fasilitasString) { // Split by comma dan trim whitespace $items = array_map('trim', explode(',', $fasilitasString)); foreach ($items as $item) { if (!empty($item)) { // Normalisasi ke lowercase untuk menghindari duplikat $fasilitasUnique->push(strtolower($item)); } } } // Remove duplicates (case-insensitive), sort alphabetically, capitalize first letter $fasilitasUnique = $fasilitasUnique->unique() ->sort() ->map(function($item) { return ucfirst($item); // Kapitalisasi huruf pertama untuk tampilan }) ->values(); // Kirim data filter ke view untuk maintain state $filters = [ 'search' => $request->search, 'harga_min' => $request->harga_min ?? $hargaMin, 'harga_max' => $request->harga_max ?? $hargaMax, 'jarak_max' => $request->jarak_max, 'jumlah_kamar_min' => $request->jumlah_kamar_min, 'jumlah_kamar_max' => $request->jumlah_kamar_max, 'fasilitas_filter' => $request->fasilitas_filter ?? [], 'sort_by' => $sortBy, ]; return view('kontrakan.index', compact( 'kontrakan', 'filters', 'totalKontrakan', 'filteredCount', 'hargaMin', 'hargaMax', 'jarakMaxKm', 'jumlah_kamarMin', 'jumlah_kamarMax', 'fasilitasUnique' )); } /** * Show the form for creating a new kontrakan */ public function create() { return view('kontrakan.create'); } /** * Store a newly created kontrakan in storage */ public function store(Request $request) { // Validasi input (DENGAN WHATSAPP) $request->validate([ 'nama' => 'required|string|max:255', 'alamat' => 'required|string', 'no_whatsapp' => 'nullable|string|max:20|regex:/^[0-9]+$/', 'latitude' => 'required|numeric|between:-90,90', 'longitude' => 'required|numeric|between:-180,180', 'harga' => 'required|numeric', 'jarak' => 'required|numeric', 'fasilitas' => 'nullable|string', 'jumlah_kamar' => 'required|numeric', 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', ], [ 'latitude.required' => 'Koordinat latitude harus diisi (klik pada peta)', 'longitude.required' => 'Koordinat longitude harus diisi (klik pada peta)', 'no_whatsapp.regex' => 'Nomor WhatsApp hanya boleh berisi angka', ]); // Proses Upload Foto $filename = null; if ($request->hasFile('foto')) { $file = $request->file('foto'); $filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension(); $file->move(public_path('uploads/kontrakan'), $filename); } // Simpan data ke database (DENGAN WHATSAPP & ADMIN_ID) $kontrakan = Kontrakan::create([ 'admin_id' => auth()->guard('admin')->id(), 'nama' => $request->nama, 'alamat' => $request->alamat, 'no_whatsapp' => $request->no_whatsapp, 'latitude' => $request->latitude, 'longitude' => $request->longitude, 'harga' => $request->harga, 'jarak' => $request->jarak, 'fasilitas' => $request->fasilitas, 'jumlah_kamar' => $request->jumlah_kamar, 'bathroom_count' => $request->bathroom_count ?? 1, 'foto' => $filename, 'availability_confirmed_at' => now(), ]); // Log activity ActivityLog::log('create', "Membuat kontrakan baru: {$kontrakan->nama}", 'Kontrakan', $kontrakan->id); return redirect()->route('kontrakan.index')->with('success', 'Data berhasil ditambahkan!'); } /** * Display the specified kontrakan */ public function show($id) { $kontrakan = Kontrakan::find($id); if (!$kontrakan) { return redirect()->route('kontrakan.index') ->with('error', 'Data kontrakan tidak ditemukan!'); } // Cek apakah user yang akses adalah admin atau user biasa if (auth()->check() && in_array(auth()->user()->role, ['super_admin', 'admin'])) { // Admin: tampilkan view admin dengan fitur edit/delete return view('kontrakan.show', compact('kontrakan')); } else { // User biasa atau guest: tampilkan view user-friendly tanpa fitur admin return view('user.kontrakan-detail', compact('kontrakan')); } } /** * Show the form for editing the specified kontrakan */ public function edit($id) { $kontrakan = Kontrakan::find($id); if (!$kontrakan) { return redirect()->route('kontrakan.index') ->with('error', 'Data kontrakan tidak ditemukan!'); } // ========== AUTHORIZATION: Cek apakah admin punya akses ke kontrakan ini ========== $admin = auth()->guard('admin')->user(); if ($admin && $admin->role !== 'super_admin') { if ($kontrakan->admin_id !== $admin->id) { abort(403, 'Anda tidak memiliki akses ke kontrakan ini.'); } } return view('kontrakan.edit', compact('kontrakan')); } /** * Update the specified kontrakan in storage */ public function update(Request $request, $id) { $kontrakan = Kontrakan::find($id); if (!$kontrakan) { return redirect()->route('kontrakan.index') ->with('error', 'Data kontrakan tidak ditemukan!'); } // ========== AUTHORIZATION: Cek apakah admin punya akses ke kontrakan ini ========== $admin = auth()->guard('admin')->user(); if ($admin && $admin->role !== 'super_admin') { if ($kontrakan->admin_id !== $admin->id) { abort(403, 'Anda tidak memiliki akses ke kontrakan ini.'); } } // Validasi input (DENGAN WHATSAPP) $request->validate([ 'nama' => 'required|string|max:255', 'alamat' => 'required|string', 'no_whatsapp' => 'nullable|string|max:20|regex:/^[0-9]+$/', 'latitude' => 'required|numeric|between:-90,90', 'longitude' => 'required|numeric|between:-180,180', 'harga' => 'required|numeric', 'jarak' => 'required|numeric', 'fasilitas' => 'nullable|string', 'jumlah_kamar' => 'required|numeric', 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', ], [ 'latitude.required' => 'Koordinat latitude harus diisi (klik pada peta)', 'longitude.required' => 'Koordinat longitude harus diisi (klik pada peta)', 'no_whatsapp.regex' => 'Nomor WhatsApp hanya boleh berisi angka', ]); // Default: foto tetap sama $filename = $kontrakan->foto; // Cek apakah user ingin HAPUS FOTO if ($request->has('hapus_foto') && $request->hapus_foto == '1') { if ($kontrakan->foto && File::exists(public_path('uploads/kontrakan/' . $kontrakan->foto))) { File::delete(public_path('uploads/kontrakan/' . $kontrakan->foto)); } $filename = null; } // Cek apakah ada foto baru diupload elseif ($request->hasFile('foto')) { if ($kontrakan->foto && File::exists(public_path('uploads/kontrakan/' . $kontrakan->foto))) { File::delete(public_path('uploads/kontrakan/' . $kontrakan->foto)); } $file = $request->file('foto'); $filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension(); $file->move(public_path('uploads/kontrakan'), $filename); } // Update data (DENGAN WHATSAPP) $oldValues = $kontrakan->toArray(); $kontrakan->update([ 'nama' => $request->nama, 'alamat' => $request->alamat, 'no_whatsapp' => $request->no_whatsapp, 'latitude' => $request->latitude, 'longitude' => $request->longitude, 'harga' => $request->harga, 'jarak' => $request->jarak, 'fasilitas' => $request->fasilitas, 'jumlah_kamar' => $request->jumlah_kamar, 'bathroom_count' => $request->bathroom_count ?? $kontrakan->bathroom_count ?? 1, 'foto' => $filename, 'availability_confirmed_at' => now(), ]); // Log activity ActivityLog::log('update', "Memperbarui kontrakan: {$kontrakan->nama}", 'Kontrakan', $kontrakan->id, $oldValues, $kontrakan->toArray()); return redirect()->route('kontrakan.index')->with('success', 'Data berhasil diperbarui!'); } /** * Remove the specified kontrakan from storage */ public function destroy($id) { // Cari kontrakan berdasarkan ID $kontrakan = Kontrakan::find($id); if (!$kontrakan) { return redirect()->route('kontrakan.index') ->with('error', 'Data kontrakan tidak ditemukan atau sudah dihapus sebelumnya!'); } // ========== AUTHORIZATION: Cek apakah admin punya akses ke kontrakan ini ========== $admin = auth()->guard('admin')->user(); if ($admin && $admin->role !== 'super_admin') { if ($kontrakan->admin_id !== $admin->id) { abort(403, 'Anda tidak memiliki akses untuk menghapus kontrakan ini.'); } } // Hapus foto dari folder sebelum hapus data if ($kontrakan->foto && File::exists(public_path('uploads/kontrakan/' . $kontrakan->foto))) { File::delete(public_path('uploads/kontrakan/' . $kontrakan->foto)); } // Store nama kontrakan untuk logging sebelum dihapus $kontrakanNama = $kontrakan->nama; $kontrakanData = $kontrakan->toArray(); // Hapus data dari database $kontrakan->delete(); // Log activity ActivityLog::log('delete', "Menghapus kontrakan: {$kontrakanNama}", 'Kontrakan', $kontrakan->id, $kontrakanData, []); return redirect()->route('kontrakan.index')->with('success', 'Data berhasil dihapus!'); } /** * Bulk delete multiple kontrakan */ public function bulkDestroy(Request $request) { // Handle both JSON string and array input $ids = $request->ids; if (is_string($ids)) { $ids = json_decode($ids, true); } if (empty($ids) || !is_array($ids)) { return redirect()->route('kontrakan.index') ->with('error', 'Pilih minimal 1 kontrakan untuk dihapus!'); } // Merge decoded ids back to request for validation $request->merge(['ids' => $ids]); $deletedCount = 0; $deletedNames = []; $admin = auth()->guard('admin')->user(); // Loop dan hapus satu per satu foreach ($request->ids as $id) { $kontrakan = Kontrakan::find($id); if ($kontrakan) { // ========== AUTHORIZATION: Cek apakah admin punya akses ke kontrakan ini ========== if ($admin && $admin->role !== 'super_admin') { if ($kontrakan->admin_id !== $admin->id) { continue; // Skip kontrakan yang bukan milik admin } } $deletedNames[] = $kontrakan->nama; // Hapus foto jika ada if ($kontrakan->foto && File::exists(public_path('uploads/kontrakan/' . $kontrakan->foto))) { File::delete(public_path('uploads/kontrakan/' . $kontrakan->foto)); } // Hapus data $kontrakan->delete(); $deletedCount++; // Log activity untuk setiap deletion ActivityLog::log('delete', "Menghapus kontrakan: {$kontrakan->nama} (bulk)", 'Kontrakan', $id); } } return redirect()->route('kontrakan.index') ->with('success', "Berhasil menghapus {$deletedCount} data kontrakan!"); } /** * Update status kontrakan (Quick update) */ public function updateStatus(Request $request, Kontrakan $kontrakan) { $request->validate([ 'status' => 'required|in:available,booked,occupied,maintenance', ]); $oldStatus = $kontrakan->status; $newStatus = $request->status; $kontrakan->update([ 'status' => $newStatus, // Reset occupied_until jika status jadi available 'occupied_until' => $newStatus === 'available' ? null : $kontrakan->occupied_until, 'availability_confirmed_at' => now(), ]); // Log activity ActivityLog::log('update', "Mengubah status kontrakan {$kontrakan->nama}: {$oldStatus} → {$newStatus}", 'Kontrakan', $kontrakan->id); $statusLabels = [ 'available' => 'Tersedia', 'booked' => 'Sudah Dipesan', 'occupied' => 'Sedang Ditempati', 'maintenance' => 'Pemeliharaan', ]; return back()->with('success', "Status kontrakan berhasil diubah menjadi \"{$statusLabels[$newStatus]}\"!"); } }