MIF_E31230069/spk_kontrakan/app/Http/Controllers/KontrakanController.php

506 lines
19 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Kontrakan;
use App\Models\ActivityLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
class KontrakanController extends Controller
{
/**
* Display a listing of kontrakan with ADVANCED filters and sorting
*/
public function index(Request $request)
{
// Query builder
$query = Kontrakan::query();
// ========== FILTER: ADMIN (Hanya kontrakan milik admin yang login) ==========
$admin = auth()->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]}\"!");
}
}