diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index 5d17a49..2c25aaa 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -8,6 +8,7 @@ use App\Models\Paket; use App\Models\Sewa; use Carbon\Carbon; +use Illuminate\Support\Facades\Auth; class DashboardController extends Controller { @@ -17,14 +18,22 @@ public function index() $stats = [ 'users' => User::where('role', 'customer')->count(), 'packages' => Paket::count(), - 'rentals' => Sewa::count(), + 'rentals' => Sewa::whereIn('status', ['disetujui', 'selesai'])->count(), 'revenue' => Sewa::where('status', 'selesai')->sum('total_harga') ]; // Mengambil aktivitas terbaru $activities = $this->getRecentActivities(); - return view('admin.dashboard', compact('stats', 'activities')); + // Mengambil pesanan yang dibatalkan terbaru (untuk notifikasi) + $recentCancellations = Sewa::with(['user', 'paket']) + ->where('status', 'dibatalkan') + ->whereNotNull('detail_status') + ->latest() + ->take(5) + ->get(); + + return view('admin.dashboard', compact('stats', 'activities', 'recentCancellations')); } private function getRecentActivities() @@ -33,6 +42,7 @@ private function getRecentActivities() // Aktivitas sewa terbaru $recentRentals = Sewa::with(['user', 'paket']) + ->whereIn('status', ['disetujui', 'selesai']) ->latest() ->take(5) ->get(); @@ -63,6 +73,23 @@ private function getRecentActivities() ]; } + // Aktivitas pembatalan + $recentCancellations = Sewa::with(['user', 'paket']) + ->where('status', 'dibatalkan') + ->latest() + ->take(3) + ->get(); + + foreach ($recentCancellations as $cancellation) { + $activities[] = [ + 'title' => 'Pesanan Dibatalkan', + 'description' => "{$cancellation->user->name} membatalkan pesanan paket {$cancellation->paket->nama_paket}", + 'time' => Carbon::parse($cancellation->updated_at)->diffForHumans(), + 'color' => 'red', + 'icon' => 'M6 18L18 6M6 6l12 12' + ]; + } + // Urutkan berdasarkan waktu terbaru usort($activities, function($a, $b) { return strtotime($b['time']) - strtotime($a['time']); @@ -70,4 +97,86 @@ private function getRecentActivities() return array_slice($activities, 0, 5); } + + /** + * Menampilkan halaman daftar pesanan yang dibatalkan + */ + public function cancelledOrders() + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return redirect()->route('dashboard')->with('error', 'Akses ditolak.'); + } + + $cancelledOrders = Sewa::with(['user', 'paket', 'kota']) + ->where('status', 'dibatalkan') + ->orderBy('updated_at', 'desc') + ->paginate(15); + + return view('admin.cancelled-orders', compact('cancelledOrders')); + } + + /** + * Menampilkan detail pesanan yang dibatalkan + */ + public function showCancelledOrder($id) + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return redirect()->route('dashboard')->with('error', 'Akses ditolak.'); + } + + $sewa = Sewa::with(['user', 'paket', 'kota'])->findOrFail($id); + + if ($sewa->status !== 'dibatalkan') { + return redirect()->route('admin.cancelled-orders')->with('error', 'Pesanan ini tidak dibatalkan.'); + } + + return view('admin.cancelled-order-detail', compact('sewa')); + } + + /** + * API untuk mendapatkan jumlah pesanan yang dibatalkan (untuk notifikasi) + */ + public function getCancelledOrdersCount() + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return response()->json(['error' => 'Unauthorized'], 403); + } + + $count = Sewa::where('status', 'dibatalkan') + ->whereNotNull('detail_status') + ->where('updated_at', '>=', now()->subDays(7)) // Hanya 7 hari terakhir + ->count(); + + return response()->json(['count' => $count]); + } + + /** + * API untuk mendapatkan notifikasi pembatalan terbaru + */ + public function getRecentCancellations() + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return response()->json(['error' => 'Unauthorized'], 403); + } + + $recentCancellations = Sewa::with(['user', 'paket']) + ->where('status', 'dibatalkan') + ->whereNotNull('detail_status') + ->where('updated_at', '>=', now()->subHours(24)) // Hanya 24 jam terakhir + ->orderBy('updated_at', 'desc') + ->take(5) + ->get() + ->map(function ($cancellation) { + return [ + 'id' => $cancellation->id, + 'user_name' => $cancellation->user->name ?? 'N/A', + 'paket_name' => $cancellation->paket->nama ?? 'N/A', + 'alasan' => $cancellation->detail_status, + 'cancelled_at' => $cancellation->updated_at->diffForHumans(), + 'total_harga' => number_format($cancellation->total_harga, 0, ',', '.') + ]; + }); + + return response()->json(['cancellations' => $recentCancellations]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/VerifikasiController.php b/app/Http/Controllers/Admin/VerifikasiController.php index cf17fd7..86ce771 100644 --- a/app/Http/Controllers/Admin/VerifikasiController.php +++ b/app/Http/Controllers/Admin/VerifikasiController.php @@ -8,6 +8,14 @@ class VerifikasiController extends Controller { + // Konstanta untuk opsi alasan penolakan + const ALASAN_PENOLAKAN = [ + 'Bukti pembayaran tidak valid', + 'Jaminan tidak sesuai', + 'Data tidak lengkap', + 'Lainnya' + ]; + /** * Menampilkan daftar pembayaran yang perlu diverifikasi */ @@ -51,7 +59,7 @@ public function approve($id) /** * Menolak pembayaran */ - public function reject($id) + public function reject(Request $request, $id) { try { $sewa = Sewa::findOrFail($id); @@ -61,9 +69,32 @@ public function reject($id) return back()->with('error', 'Status sewa tidak valid untuk ditolak.'); } - // Update status menjadi ditolak + // Validasi alasan penolakan + $request->validate([ + 'alasan_penolakan' => 'required|string|max:255', + 'alasan_lainnya' => 'nullable|string|max:255' + ]); + + // Ambil alasan penolakan + $alasan = trim($request->alasan_penolakan); + if ($alasan === 'Lainnya') { + $alasanLainnya = trim($request->alasan_lainnya ?? ''); + if (empty($alasanLainnya)) { + return back()->with('error', 'Silakan isi alasan lainnya.'); + } + $alasan = $alasanLainnya; + } + + // Pastikan alasan tidak kosong + if (empty($alasan)) { + return back()->with('error', 'Alasan penolakan tidak boleh kosong.'); + } + + // Update status menjadi ditolak dan simpan alasan ke detail_status $sewa->update([ - 'status' => 'ditolak' + 'status' => 'ditolak', + 'detail_status' => $alasan, + 'catatan' => 'Pembayaran ditolak: ' . $alasan ]); return back()->with('success', 'Pembayaran telah ditolak.'); @@ -71,4 +102,14 @@ public function reject($id) return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } + + /** + * Mendapatkan daftar alasan penolakan + */ + public function getAlasanPenolakan() + { + return response()->json([ + 'alasan_penolakan' => self::ALASAN_PENOLAKAN + ]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Customer/DashboardController.php b/app/Http/Controllers/Customer/DashboardController.php index 4e2db06..22c3f4a 100644 --- a/app/Http/Controllers/Customer/DashboardController.php +++ b/app/Http/Controllers/Customer/DashboardController.php @@ -18,7 +18,9 @@ public function index() 'active_rentals' => Sewa::where('user_id', $user->id) ->whereIn('status', ['menunggu', 'diproses']) ->count(), - 'total_rentals' => Sewa::where('user_id', $user->id)->count(), + 'total_rentals' => Sewa::where('user_id', $user->id) + ->whereIn('status', ['disetujui', 'selesai']) + ->count(), 'total_spent' => Sewa::where('user_id', $user->id) ->where('status', 'selesai') ->sum('total_harga') @@ -34,6 +36,7 @@ private function getRecentRentals($userId) { $rentals = Sewa::with('paket') ->where('user_id', $userId) + ->whereIn('status', ['disetujui', 'selesai']) ->latest() ->take(5) ->get(); diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index ab9e416..959c2cd 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -22,21 +22,26 @@ public function index() // Cek tipe pengguna yang sedang login $userType = Auth::user()->tipe_pengguna; - // Data untuk dashboard + // Jika user adalah customer, redirect langsung ke halaman sewa + if ($userType === 'user') { + return redirect()->route('sewa.index'); + } + + // Data untuk dashboard (hanya untuk admin) $stats = [ 'pengguna' => User::where('tipe_pengguna', 'user')->count(), 'admin' => User::where('tipe_pengguna', 'admin')->count(), 'total_pengguna' => User::count(), 'barang' => Barang::count(), - 'sewa' => DB::table('sewas')->count() + 'sewa' => DB::table('sewas')->whereIn('status', ['disetujui', 'selesai'])->count() ]; // Data grafik per tahun $yearlyStats = DB::table('sewas') ->select( DB::raw('YEAR(created_at) as tahun'), - DB::raw('COUNT(*) as total_sewa'), - DB::raw('SUM(total_harga) as total_pemasukan'), + DB::raw('COUNT(CASE WHEN status IN ("disetujui", "selesai") THEN 1 END) as total_sewa'), + DB::raw('SUM(CASE WHEN status IN ("disetujui", "selesai") THEN total_harga ELSE 0 END) as total_pemasukan'), DB::raw('0 as total_pengeluaran') // Sementara set 0 karena belum ada kolom biaya_operasional ) ->groupBy('tahun') @@ -121,63 +126,35 @@ public function index() ] ]; - // Debug data - // dd($chartData, $userChartData); - - // Data aktivitas + // Data aktivitas (hanya untuk admin) $aktivitas = []; - // Jika user adalah admin, tampilkan semua data - if ($userType === 'admin') { - // Ambil user terbaru yang mendaftar (baik admin maupun user) - $latestUsers = User::latest()->take(5)->get(); - - foreach ($latestUsers as $user) { - $aktivitas[] = [ - 'type' => 'pengguna', - 'title' => 'Pengguna ' . ucfirst($user->tipe_pengguna), - 'description' => $user->nama . ' (' . $user->tipe_pengguna . ')', - 'time' => Carbon::parse($user->created_at)->diffForHumans(), - 'icon_color' => $user->tipe_pengguna === 'admin' ? 'indigo' : 'blue' - ]; - } - - // Tambahkan aktivitas sewa terbaru - $latestSewa = Sewa::with(['user', 'paket'])->latest()->first(); - if ($latestSewa) { - $aktivitas[] = [ - 'type' => 'sewa', - 'title' => 'Pemesanan Baru', - 'description' => $latestSewa->user->nama . ' memesan paket ' . $latestSewa->paket->nama_paket, - 'time' => Carbon::parse($latestSewa->created_at)->diffForHumans(), - 'icon_color' => 'green' - ]; - } - } else { - // Jika user adalah customer, hanya tampilkan data terbatas + // Ambil user terbaru yang mendaftar (baik admin maupun user) + $latestUsers = User::latest()->take(5)->get(); + + foreach ($latestUsers as $user) { $aktivitas[] = [ - 'type' => 'info', - 'title' => 'Selamat Datang', - 'description' => 'Anda login sebagai pengguna biasa. Anda hanya dapat melihat informasi.', - 'time' => 'Saat ini', - 'icon_color' => 'blue' + 'type' => 'pengguna', + 'title' => 'Pengguna ' . ucfirst($user->tipe_pengguna), + 'description' => $user->nama . ' (' . $user->tipe_pengguna . ')', + 'time' => Carbon::parse($user->created_at)->diffForHumans(), + 'icon_color' => $user->tipe_pengguna === 'admin' ? 'indigo' : 'blue' + ]; + } + + // Tambahkan aktivitas sewa terbaru + $latestSewa = Sewa::with(['user', 'paket']) + ->whereIn('status', ['disetujui', 'selesai']) + ->latest() + ->first(); + if ($latestSewa) { + $aktivitas[] = [ + 'type' => 'sewa', + 'title' => 'Pemesanan Baru', + 'description' => $latestSewa->user->nama . ' memesan paket ' . $latestSewa->paket->nama_paket, + 'time' => Carbon::parse($latestSewa->created_at)->diffForHumans(), + 'icon_color' => 'green' ]; - - // Tampilkan riwayat sewa pengguna ini saja - $userSewa = Sewa::where('user_id', Auth::id()) - ->with('paket') - ->latest() - ->first(); - - if ($userSewa) { - $aktivitas[] = [ - 'type' => 'sewa', - 'title' => 'Pesanan Terakhir Anda', - 'description' => 'Paket: ' . $userSewa->paket->nama_paket, - 'time' => Carbon::parse($userSewa->created_at)->diffForHumans(), - 'icon_color' => 'green' - ]; - } } return view('dashboard', compact('stats', 'chartData', 'userChartData', 'aktivitas', 'userType')); diff --git a/app/Http/Controllers/PaketController.php b/app/Http/Controllers/PaketController.php index 9fae0df..a072438 100644 --- a/app/Http/Controllers/PaketController.php +++ b/app/Http/Controllers/PaketController.php @@ -209,7 +209,7 @@ public function update(Request $request, $id) } DB::commit(); - return redirect()->route('paket')->with('success', 'Paket berhasil diperbarui!'); + return redirect()->route('paket.index')->with('success', 'Paket berhasil diperbarui!'); } catch (\Exception $e) { DB::rollback(); @@ -402,17 +402,17 @@ public function activate($id) ->first(); if (!$oldestSewa) { - return redirect()->route('paket')->with('error', 'Tidak ada penyewaan aktif yang dapat diselesaikan.'); + return redirect()->route('paket.index')->with('error', 'Tidak ada penyewaan aktif yang dapat diselesaikan.'); } - // Update status sewa menjadi completed hanya untuk sewa terlama - $oldestSewa->update(['status' => 'completed']); + // Update status sewa menjadi selesai hanya untuk sewa terlama + $oldestSewa->update(['status' => 'selesai']); DB::commit(); - return redirect()->route('paket')->with('success', 'Satu unit paket berhasil diaktifkan kembali.'); + return redirect()->route('paket.index')->with('success', 'Satu unit paket berhasil diaktifkan kembali.'); } catch (\Exception $e) { DB::rollback(); - return redirect()->route('paket')->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + return redirect()->route('paket.index')->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } @@ -426,20 +426,20 @@ public function activateAll($id) $paket = Paket::findOrFail($id); - // Update status semua sewa yang masih aktif menjadi completed + // Update status semua sewa yang masih aktif menjadi selesai $count = $paket->sewas() ->whereIn('status', ['confirmed', 'ongoing']) - ->update(['status' => 'completed']); + ->update(['status' => 'selesai']); if ($count === 0) { - return redirect()->route('paket')->with('error', 'Tidak ada penyewaan aktif yang dapat diselesaikan.'); + return redirect()->route('paket.index')->with('error', 'Tidak ada penyewaan aktif yang dapat diselesaikan.'); } DB::commit(); - return redirect()->route('paket')->with('success', 'Semua unit paket berhasil diaktifkan kembali.'); + return redirect()->route('paket.index')->with('success', 'Semua unit paket berhasil diaktifkan kembali.'); } catch (\Exception $e) { DB::rollback(); - return redirect()->route('paket')->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + return redirect()->route('paket.index')->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } diff --git a/app/Http/Controllers/PenggunaController.php b/app/Http/Controllers/PenggunaController.php index f7964b6..5cc3497 100644 --- a/app/Http/Controllers/PenggunaController.php +++ b/app/Http/Controllers/PenggunaController.php @@ -12,6 +12,9 @@ class PenggunaController extends Controller { public function __construct() { + // Middleware untuk memastikan user sudah login + $this->middleware('auth'); + // Middleware untuk method yang membutuhkan akses admin $this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']); } diff --git a/app/Http/Controllers/RiwayatController.php b/app/Http/Controllers/RiwayatController.php index 2b56d1a..54174dc 100644 --- a/app/Http/Controllers/RiwayatController.php +++ b/app/Http/Controllers/RiwayatController.php @@ -7,6 +7,8 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Log; class RiwayatController extends Controller { @@ -21,6 +23,23 @@ public function index() return view('riwayat', compact('sewas')); } + /** + * Menampilkan riwayat untuk admin + */ + public function adminIndex() + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return redirect()->route('dashboard')->with('error', 'Akses ditolak.'); + } + + $sewas = Sewa::with(['paket', 'kota', 'user']) + ->where('status', '!=', 'draft') + ->orderBy('created_at', 'desc') + ->get(); + + return view('admin.riwayat', compact('sewas')); + } + public function updateStatus(Request $request, $id) { if (Auth::user()->tipe_pengguna !== 'admin') { @@ -28,7 +47,7 @@ public function updateStatus(Request $request, $id) } $request->validate([ - 'status' => 'required|in:completed' + 'status' => 'required|in:selesai' ]); $sewa = Sewa::findOrFail($id); @@ -40,7 +59,7 @@ public function updateStatus(Request $request, $id) $sewa->status = $request->status; $sewa->save(); - return back()->with('success', 'Status berhasil diperbarui menjadi completed.'); + return back()->with('success', 'Status berhasil diperbarui menjadi selesai. Stok paket telah diaktifkan kembali.'); } public function hapus($id) @@ -52,7 +71,7 @@ public function hapus($id) ->with('error', 'Anda tidak memiliki akses untuk menghapus pesanan ini.'); } - if (!in_array($sewa->status, ['completed', 'dibatalkan'])) { + if (!in_array($sewa->status, ['selesai', 'dibatalkan'])) { return redirect()->route('riwayat') ->with('error', 'Hanya pesanan yang sudah selesai atau dibatalkan yang dapat dihapus.'); } @@ -70,8 +89,35 @@ public function hapus($id) ->with('success', 'Riwayat pesanan berhasil dihapus.'); } + /** + * Mengaktifkan stok paket dari halaman riwayat + */ + public function activateStock($id) + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return redirect()->route('dashboard')->with('error', 'Akses ditolak.'); + } + + try { + $sewa = Sewa::with('paket')->findOrFail($id); + + // Pastikan status sewa adalah confirmed atau ongoing + if (!in_array($sewa->status, ['confirmed', 'ongoing'])) { + return back()->with('error', 'Hanya sewa dengan status confirmed atau ongoing yang dapat diaktifkan.'); + } + + // Update status sewa menjadi selesai + $sewa->update(['status' => 'selesai']); + + return back()->with('success', 'Stok paket berhasil diaktifkan kembali. Status sewa diubah menjadi selesai.'); + } catch (\Exception $e) { + return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + } + } + public function show($id) { + try { $sewa = Sewa::with(['paket', 'kota'])->findOrFail($id); // Pastikan hanya user yang berhak yang bisa akses @@ -79,18 +125,206 @@ public function show($id) return response()->json(['error' => 'Unauthorized'], 403); } - $nomor_rekening = Setting::where('key', 'nomor_rekening')->first()->value ?? null; + // Cek apakah tabel settings ada dan ambil nomor rekening dengan aman + $nomor_rekening = null; + try { + if (Schema::hasTable('settings')) { + $setting = Setting::where('key', 'nomor_rekening')->first(); + $nomor_rekening = $setting ? $setting->value : null; + } + } catch (\Exception $e) { + // Jika ada error saat mengakses tabel settings, set null + $nomor_rekening = null; + } return response()->json([ 'tanggal_pembayaran' => $sewa->tanggal_pembayaran, 'status_pembayaran' => $sewa->status, + 'detail_status' => $sewa->detail_status, 'lokasi' => $sewa->lokasi, - 'kota' => $sewa->kota, + 'kota' => $sewa->kota ?? null, 'ongkir' => $sewa->ongkir, 'bukti_pembayaran' => $sewa->bukti_pembayaran, 'foto_jaminan' => $sewa->foto_jaminan, 'jenis_jaminan' => $sewa->jenis_jaminan, 'nomor_rekening' => $nomor_rekening, + 'paket' => $sewa->paket ?? null, ]); + } catch (\Exception $e) { + return response()->json([ + 'error' => 'Terjadi kesalahan: ' . $e->getMessage() + ], 500); + } + } + + /** + * Membatalkan pesanan + */ + public function cancel(Request $request, $id) + { + try { + $sewa = Sewa::findOrFail($id); + + // Pastikan hanya user yang berhak yang bisa membatalkan + if ($sewa->user_id != Auth::id()) { + if ($request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => 'Anda tidak memiliki akses untuk membatalkan pesanan ini.' + ], 403); + } + return redirect()->route('riwayat') + ->with('error', 'Anda tidak memiliki akses untuk membatalkan pesanan ini.'); + } + + // Cek apakah status memungkinkan untuk dibatalkan + if (in_array($sewa->status, ['selesai', 'dibatalkan', 'ongoing'])) { + if ($request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => 'Pesanan dengan status ini tidak dapat dibatalkan.' + ], 400); + } + return redirect()->route('riwayat') + ->with('error', 'Pesanan dengan status ini tidak dapat dibatalkan.'); + } + + // Ambil alasan pembatalan dari request + $alasanPembatalan = $request->input('alasan_pembatalan', 'Tidak ada alasan yang diberikan'); + + // Update status menjadi dibatalkan dan simpan alasan + $sewa->status = 'dibatalkan'; + $sewa->detail_status = $alasanPembatalan; + $sewa->save(); + + // Log aktivitas pembatalan untuk dashboard admin + Log::info('Pesanan dibatalkan dengan alasan', [ + 'sewa_id' => $sewa->id, + 'user_id' => $sewa->user_id, + 'user_name' => Auth::user()->name, + 'paket_name' => $sewa->paket->nama ?? 'N/A', + 'alasan_pembatalan' => $alasanPembatalan, + 'cancelled_at' => now() + ]); + + if ($request->ajax()) { + return response()->json([ + 'success' => true, + 'message' => 'Pesanan berhasil dibatalkan. Alasan pembatalan telah dikirim ke admin.', + 'data' => [ + 'sewa_id' => $sewa->id, + 'status' => $sewa->status, + 'alasan' => $alasanPembatalan + ] + ]); + } + + return redirect()->route('riwayat') + ->with('success', 'Pesanan berhasil dibatalkan. Alasan pembatalan telah dikirim ke admin.'); + + } catch (\Exception $e) { + if ($request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat membatalkan pesanan: ' . $e->getMessage() + ], 500); + } + + return redirect()->route('riwayat') + ->with('error', 'Terjadi kesalahan saat membatalkan pesanan: ' . $e->getMessage()); + } + } + + /** + * Menampilkan form pembatalan + */ + public function showCancelForm($id) + { + try { + $sewa = Sewa::with(['paket', 'kota'])->findOrFail($id); + + // Pastikan hanya user yang berhak yang bisa akses + if ($sewa->user_id != Auth::id()) { + return redirect()->route('riwayat') + ->with('error', 'Anda tidak memiliki akses untuk membatalkan pesanan ini.'); + } + + // Cek apakah status memungkinkan untuk dibatalkan + if (in_array($sewa->status, ['selesai', 'dibatalkan', 'ongoing'])) { + return redirect()->route('riwayat') + ->with('error', 'Pesanan dengan status ini tidak dapat dibatalkan.'); + } + + return view('riwayat.cancel-form', compact('sewa')); + + } catch (\Exception $e) { + return redirect()->route('riwayat') + ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + } + } + + /** + * Memproses pembatalan pesanan + */ + public function processCancel(Request $request, $id) + { + try { + $request->validate([ + 'alasan_pembatalan' => 'required|string|max:500' + ]); + + $sewa = Sewa::findOrFail($id); + + // Pastikan hanya user yang berhak yang bisa membatalkan + if ($sewa->user_id != Auth::id()) { + return redirect()->route('riwayat') + ->with('error', 'Anda tidak memiliki akses untuk membatalkan pesanan ini.'); + } + + // Cek apakah status memungkinkan untuk dibatalkan + if (in_array($sewa->status, ['selesai', 'dibatalkan', 'ongoing'])) { + return redirect()->route('riwayat') + ->with('error', 'Pesanan dengan status ini tidak dapat dibatalkan.'); + } + + // Update status menjadi dibatalkan dan simpan alasan + $sewa->status = 'dibatalkan'; + $sewa->detail_status = $request->alasan_pembatalan; + $sewa->save(); + + // Log aktivitas pembatalan untuk dashboard admin + Log::info('Pesanan dibatalkan dengan alasan', [ + 'sewa_id' => $sewa->id, + 'user_id' => $sewa->user_id, + 'user_name' => Auth::user()->name, + 'paket_name' => $sewa->paket->nama ?? 'N/A', + 'alasan_pembatalan' => $request->alasan_pembatalan, + 'cancelled_at' => now() + ]); + + return redirect()->route('riwayat') + ->with('success', 'Pesanan berhasil dibatalkan.'); + + } catch (\Exception $e) { + return redirect()->route('riwayat') + ->with('error', 'Terjadi kesalahan saat membatalkan pesanan: ' . $e->getMessage()); + } + } + + /** + * Menampilkan form update pembayaran + */ + public function showUpdatePaymentForm($id) + { + if (Auth::user()->tipe_pengguna !== 'admin') { + return redirect()->route('dashboard')->with('error', 'Akses ditolak.'); + } + + try { + $sewa = Sewa::with(['paket', 'kota', 'user'])->findOrFail($id); + return view('admin.update-payment-form', compact('sewa')); + } catch (\Exception $e) { + return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + } } } diff --git a/app/Http/Controllers/SewaController.php b/app/Http/Controllers/SewaController.php index 7d20642..a2657b7 100644 --- a/app/Http/Controllers/SewaController.php +++ b/app/Http/Controllers/SewaController.php @@ -56,11 +56,7 @@ public function create($id) $paket = Paket::with('ongkirKota')->findOrFail($id); // Cek apakah paket tersedia - $activeSewa = $paket->sewas() - ->whereIn('status', ['confirmed', 'ongoing']) - ->count(); - - if ($activeSewa >= $paket->stok) { + if (!$paket->is_available) { return redirect()->route('sewa.index') ->with('error', 'Maaf, paket ini sedang tidak tersedia.'); } @@ -89,12 +85,16 @@ public function store(Request $request) try { $paket = Paket::findOrFail($request->paket_id); - // Cek ketersediaan paket + // Cek ketersediaan paket untuk tanggal yang dipilih $activeSewa = $paket->sewas() ->whereIn('status', ['confirmed', 'ongoing']) ->where(function($query) use ($request) { $query->whereBetween('tanggal_mulai', [$request->tanggal_mulai, $request->tanggal_selesai]) - ->orWhereBetween('tanggal_selesai', [$request->tanggal_mulai, $request->tanggal_selesai]); + ->orWhereBetween('tanggal_selesai', [$request->tanggal_mulai, $request->tanggal_selesai]) + ->orWhere(function($q) use ($request) { + $q->where('tanggal_mulai', '<=', $request->tanggal_mulai) + ->where('tanggal_selesai', '>=', $request->tanggal_selesai); + }); }) ->count(); @@ -309,4 +309,34 @@ public function detail($id) ], 500); } } + + public function detailJson($id) + { + $sewa = Sewa::with(['paket.barangs', 'user', 'kota'])->findOrFail($id); + return response()->json([ + 'id' => $sewa->id, + 'paket' => [ + 'nama_paket' => $sewa->paket->nama_paket, + 'jenis_paket' => $sewa->paket->jenis_paket, + 'harga' => $sewa->paket->harga, + ], + 'user' => [ + 'nama' => $sewa->user->nama, + 'email' => $sewa->user->email, + ], + 'tanggal_mulai' => $sewa->tanggal_mulai, + 'tanggal_selesai' => $sewa->tanggal_selesai, + 'status' => $sewa->status, + 'total_harga' => $sewa->total_harga, + 'lokasi' => $sewa->lokasi, + 'kota' => $sewa->kota ? $sewa->kota->nama_kota : null, + 'catatan' => $sewa->catatan, + 'barangs' => $sewa->paket->barangs->map(function($barang) { + return [ + 'nama_barang' => $barang->nama_barang, + 'jumlah' => $barang->pivot->jumlah + ]; + }), + ]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 709f712..a89b0e8 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -10,6 +10,9 @@ class UserController extends Controller { public function __construct() { + // Middleware untuk memastikan user sudah login + $this->middleware('auth'); + // Middleware hanya untuk admin yang mengakses destroy $this->middleware('admin')->only(['destroy']); } diff --git a/app/Models/Paket.php b/app/Models/Paket.php index ac0d1aa..7f7de83 100644 --- a/app/Models/Paket.php +++ b/app/Models/Paket.php @@ -123,4 +123,46 @@ public function sewas() { return $this->hasMany(Sewa::class); } + + /** + * Menghitung jumlah sewa yang sedang aktif (confirmed/ongoing) + */ + public function getActiveSewaCountAttribute() + { + return $this->sewas() + ->whereIn('status', ['confirmed', 'ongoing']) + ->count(); + } + + /** + * Menghitung stok yang tersedia + */ + public function getStokTersediaAttribute() + { + return max(0, $this->stok - $this->active_sewa_count); + } + + /** + * Cek apakah paket tersedia untuk disewa + */ + public function getIsAvailableAttribute() + { + return $this->status === 'aktif' && $this->stok_tersedia > 0; + } + + /** + * Mendapatkan status ketersediaan yang diformat + */ + public function getStatusKetersediaanAttribute() + { + if ($this->status !== 'aktif') { + return 'Tidak Aktif'; + } + + if ($this->stok_tersedia <= 0) { + return 'Sedang Disewa Semua'; + } + + return "Tersedia ({$this->stok_tersedia} unit)"; + } } \ No newline at end of file diff --git a/app/Models/Sewa.php b/app/Models/Sewa.php index 12f64f8..58a648a 100644 --- a/app/Models/Sewa.php +++ b/app/Models/Sewa.php @@ -21,6 +21,7 @@ class Sewa extends Model 'catatan', 'total_harga', 'status', + 'detail_status', 'bukti_pembayaran', 'tanggal_pembayaran', 'jenis_jaminan', @@ -92,7 +93,7 @@ public function scopeStatus($query, $status) // Scope untuk sewa yang aktif public function scopeAktif($query) { - return $query->whereIn('status', ['disetujui']) + return $query->whereIn('status', ['confirmed']) ->where('tanggal_selesai', '>=', now()); } diff --git a/database/migrations/2025_01_27_000000_add_detail_status_to_sewas_table.php b/database/migrations/2025_01_27_000000_add_detail_status_to_sewas_table.php new file mode 100644 index 0000000..6468111 --- /dev/null +++ b/database/migrations/2025_01_27_000000_add_detail_status_to_sewas_table.php @@ -0,0 +1,28 @@ +string('detail_status')->nullable()->after('status')->comment('Detail status untuk alasan penolakan atau informasi tambahan'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('sewas', function (Blueprint $table) { + $table->dropColumn('detail_status'); + }); + } +}; \ No newline at end of file diff --git a/resources/views/Tambah-Paket.blade.php b/resources/views/Tambah-Paket.blade.php index 89574fc..2bee365 100644 --- a/resources/views/Tambah-Paket.blade.php +++ b/resources/views/Tambah-Paket.blade.php @@ -91,12 +91,12 @@
{{ $sewa->user->name ?? 'N/A' }}
+{{ $sewa->user->email ?? 'N/A' }}
+{{ $sewa->user->no_telp ?? 'N/A' }}
+{{ $sewa->user->alamat ?? 'N/A' }}
+#{{ $sewa->id }}
+{{ $sewa->paket->nama ?? 'N/A' }}
+Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}
+{{ $sewa->created_at->format('d/m/Y H:i') }}
+{{ $sewa->updated_at->format('d/m/Y H:i') }}
+{{ $sewa->detail_status }}
+Customer tidak memberikan alasan pembatalan.
+{{ $sewa->lokasi }}
+{{ $sewa->kota->nama ?? 'N/A' }}
+Rp {{ number_format($sewa->ongkir, 0, ',', '.') }}
+ID Pesanan | +Customer | +Paket | +Total Harga | +Tanggal Dibatalkan | +Alasan | +Aksi | +
---|---|---|---|---|---|---|
+ #{{ $order->id }} + | +
+ {{ $order->user->name ?? 'N/A' }}
+ + {{ $order->user->email ?? 'N/A' }} + |
+ + {{ $order->paket->nama ?? 'N/A' }} + | ++ Rp {{ number_format($order->total_harga, 0, ',', '.') }} + | +
+ {{ $order->updated_at->format('d/m/Y H:i') }}
+ + {{ $order->updated_at->diffForHumans() }} + |
+ + @if($order->detail_status) + Ada alasan + @else + Tidak ada alasan + @endif + | ++ + Lihat Detail + + | +
Belum ada customer yang membatalkan pesanan mereka.
+Ada {{ count($recentCancellations) }} pesanan yang baru dibatalkan oleh customer. + Lihat Detail
+