push tgl 9

This commit is contained in:
alfinfadli16 2025-07-09 22:06:02 +07:00
parent 6bbbe40c86
commit 1daae8d4a4
34 changed files with 1659 additions and 289 deletions

View File

@ -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]);
}
}

View File

@ -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
]);
}
}

View File

@ -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();

View File

@ -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();
// 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
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'
];
}
// 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'
];
}
// 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'
];
}
return view('dashboard', compact('stats', 'chartData', 'userChartData', 'aktivitas', 'userType'));

View File

@ -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());
}
}

View File

@ -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']);
}

View File

@ -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());
}
}
}

View File

@ -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
];
}),
]);
}
}

View File

@ -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']);
}

View File

@ -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)";
}
}

View File

@ -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());
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('sewas', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -91,12 +91,12 @@
<!-- Ongkir per Kota -->
<div class="mb-4">
<label class="block text-gray-700 font-medium mb-2">Ongkir per Kota</label>
<label class="block text-gray-700 font-medium mb-2">Ongkir per Kabupaten</label>
<div id="ongkir-container">
<div class="ongkir-form mb-2 p-4 border rounded-lg bg-gray-50">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-600">Nama Kota</label>
<label class="block text-sm font-medium text-gray-600">Nama Kabupaten</label>
<input type="text" name="ongkir[0][nama_kota]" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
@ -110,7 +110,7 @@
</div>
</div>
<button type="button" id="tambah-ongkir" class="mt-2 text-sm bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded">
<i class="fas fa-plus mr-1"></i> Tambah Kota
<i class="fas fa-plus mr-1"></i> Tambah Kabupaten
</button>
</div>
</div>

View File

@ -0,0 +1,167 @@
@extends('layouts.admin')
@section('title', 'Detail Pesanan Dibatalkan - INUFA')
@section('header', 'Detail Pesanan Dibatalkan')
@section('content')
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Detail Pesanan #{{ $sewa->id }}</h2>
<div class="flex space-x-2">
<a href="{{ route('admin.cancelled-orders') }}" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md text-sm">
Kembali ke Daftar
</a>
<a href="{{ route('admin.dashboard') }}" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md text-sm">
Dashboard
</a>
</div>
</div>
@if(session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4 rounded">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4 rounded">
{{ session('error') }}
</div>
@endif
<!-- Status Badge -->
<div class="mb-6">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
Dibatalkan oleh Customer
</span>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Informasi Customer -->
<div class="bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Informasi Customer</h3>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-600">Nama</label>
<p class="text-sm text-gray-900">{{ $sewa->user->name ?? 'N/A' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Email</label>
<p class="text-sm text-gray-900">{{ $sewa->user->email ?? 'N/A' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">No. Telepon</label>
<p class="text-sm text-gray-900">{{ $sewa->user->no_telp ?? 'N/A' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Alamat</label>
<p class="text-sm text-gray-900">{{ $sewa->user->alamat ?? 'N/A' }}</p>
</div>
</div>
</div>
<!-- Informasi Pesanan -->
<div class="bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Informasi Pesanan</h3>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-600">ID Pesanan</label>
<p class="text-sm text-gray-900">#{{ $sewa->id }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Paket</label>
<p class="text-sm text-gray-900">{{ $sewa->paket->nama ?? 'N/A' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Total Harga</label>
<p class="text-sm text-gray-900">Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Tanggal Pesanan</label>
<p class="text-sm text-gray-900">{{ $sewa->created_at->format('d/m/Y H:i') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-600">Tanggal Dibatalkan</label>
<p class="text-sm text-gray-900">{{ $sewa->updated_at->format('d/m/Y H:i') }}</p>
</div>
</div>
</div>
</div>
<!-- Alasan Pembatalan -->
<div class="mt-6 bg-red-50 border border-red-200 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-red-800 mb-4">
<svg class="w-5 h-5 inline mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
Alasan Pembatalan
</h3>
@if($sewa->detail_status)
<div class="bg-white p-4 rounded border border-red-200">
<p class="text-gray-800 leading-relaxed">{{ $sewa->detail_status }}</p>
</div>
@else
<div class="bg-white p-4 rounded border border-red-200">
<p class="text-gray-500 italic">Customer tidak memberikan alasan pembatalan.</p>
</div>
@endif
</div>
<!-- Detail Tambahan -->
@if($sewa->lokasi || $sewa->kota)
<div class="mt-6 bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Detail Lokasi</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@if($sewa->lokasi)
<div>
<label class="block text-sm font-medium text-gray-600">Lokasi</label>
<p class="text-sm text-gray-900">{{ $sewa->lokasi }}</p>
</div>
@endif
@if($sewa->kota)
<div>
<label class="block text-sm font-medium text-gray-600">Kota</label>
<p class="text-sm text-gray-900">{{ $sewa->kota->nama ?? 'N/A' }}</p>
</div>
@endif
@if($sewa->ongkir)
<div>
<label class="block text-sm font-medium text-gray-600">Ongkir</label>
<p class="text-sm text-gray-900">Rp {{ number_format($sewa->ongkir, 0, ',', '.') }}</p>
</div>
@endif
</div>
</div>
@endif
<!-- File Uploads -->
@if($sewa->bukti_pembayaran || $sewa->foto_jaminan)
<div class="mt-6 bg-gray-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-gray-800 mb-4">File yang Diupload</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@if($sewa->bukti_pembayaran)
<div>
<label class="block text-sm font-medium text-gray-600 mb-2">Bukti Pembayaran</label>
<img src="{{ asset('storage/' . $sewa->bukti_pembayaran) }}"
alt="Bukti Pembayaran"
class="w-full h-32 object-cover rounded border">
</div>
@endif
@if($sewa->foto_jaminan)
<div>
<label class="block text-sm font-medium text-gray-600 mb-2">Foto Jaminan</label>
<img src="{{ asset('storage/' . $sewa->foto_jaminan) }}"
alt="Foto Jaminan"
class="w-full h-32 object-cover rounded border">
</div>
@endif
</div>
</div>
@endif
</div>
@endsection

View File

@ -0,0 +1,97 @@
@extends('layouts.admin')
@section('title', 'Pesanan Dibatalkan - INUFA')
@section('header', 'Pesanan Dibatalkan')
@section('content')
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Daftar Pesanan Dibatalkan</h2>
<a href="{{ route('admin.dashboard') }}" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md text-sm">
Kembali ke Dashboard
</a>
</div>
@if(session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4 rounded">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4 rounded">
{{ session('error') }}
</div>
@endif
@if($cancelledOrders->count() > 0)
<div class="overflow-x-auto">
<table class="min-w-full bg-white">
<thead class="bg-gray-50">
<tr>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID Pesanan</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Paket</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Harga</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tanggal Dibatalkan</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Alasan</th>
<th class="py-3 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($cancelledOrders as $order)
<tr class="hover:bg-gray-50">
<td class="py-4 px-4 whitespace-nowrap text-sm font-medium text-gray-900">
#{{ $order->id }}
</td>
<td class="py-4 px-4 whitespace-nowrap text-sm text-gray-900">
{{ $order->user->name ?? 'N/A' }}
<br>
<span class="text-xs text-gray-500">{{ $order->user->email ?? 'N/A' }}</span>
</td>
<td class="py-4 px-4 whitespace-nowrap text-sm text-gray-900">
{{ $order->paket->nama ?? 'N/A' }}
</td>
<td class="py-4 px-4 whitespace-nowrap text-sm text-gray-900">
Rp {{ number_format($order->total_harga, 0, ',', '.') }}
</td>
<td class="py-4 px-4 whitespace-nowrap text-sm text-gray-900">
{{ $order->updated_at->format('d/m/Y H:i') }}
<br>
<span class="text-xs text-gray-500">{{ $order->updated_at->diffForHumans() }}</span>
</td>
<td class="py-4 px-4 text-sm text-gray-900">
@if($order->detail_status)
<span class="text-red-600 font-medium">Ada alasan</span>
@else
<span class="text-gray-500">Tidak ada alasan</span>
@endif
</td>
<td class="py-4 px-4 whitespace-nowrap text-sm font-medium">
<a href="{{ route('admin.cancelled-orders.show', $order->id) }}"
class="text-blue-600 hover:text-blue-900 bg-blue-100 hover:bg-blue-200 px-3 py-1 rounded-md text-xs">
Lihat Detail
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $cancelledOrders->links() }}
</div>
@else
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">Tidak ada pesanan yang dibatalkan</h3>
<p class="mt-1 text-sm text-gray-500">Belum ada customer yang membatalkan pesanan mereka.</p>
</div>
@endif
</div>
@endsection

View File

@ -107,6 +107,36 @@
</div>
@endif
<!-- Notifikasi Pesanan Dibatalkan -->
@if(isset($recentCancellations) && count($recentCancellations) > 0)
<div class="bg-red-50 border-l-4 border-red-400 p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
Pesanan Dibatalkan Terbaru
</h3>
<div class="mt-2 text-sm text-red-700">
<p>Ada {{ count($recentCancellations) }} pesanan yang baru dibatalkan oleh customer.
<a href="{{ route('admin.cancelled-orders') }}" class="font-medium underline hover:text-red-600">Lihat Detail</a></p>
</div>
<div class="mt-3">
@foreach($recentCancellations->take(3) as $cancellation)
<div class="text-xs text-red-600 mb-1">
{{ $cancellation->user->name ?? 'User' }} membatalkan pesanan #{{ $cancellation->id }}
({{ $cancellation->paket->nama ?? 'Paket' }}) - {{ $cancellation->updated_at->diffForHumans() }}
</div>
@endforeach
</div>
</div>
</div>
</div>
@endif
<div class="bg-white rounded-lg shadow-md p-6 mb-6 max-w-lg">
<h2 class="text-lg font-semibold mb-4">Pengaturan Nomor Rekening</h2>
<form action="{{ route('admin.save-rekening') }}" method="POST">

View File

@ -14,8 +14,8 @@
<div class="kota-form mb-6 p-4 border rounded-lg bg-gray-50">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-gray-700 font-medium mb-2">Nama Kota <span class="text-red-500">*</span></label>
<input type="text" name="kotas[0][nama_kota]" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<label class="block text-gray-700 font-medium mb-2">Nama Kabupaten <span class="text-red-500">*</span></label>
<input type="text" name="kotas[0][nama_Kabupaten]" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div>
<label class="block text-gray-700 font-medium mb-2">Biaya Ongkir (Rp) <span class="text-red-500">*</span></label>

View File

@ -12,7 +12,7 @@
<div class="space-y-6">
<div>
<label for="nama_kota" class="block text-gray-700 font-medium mb-2">Nama Kota <span class="text-red-500">*</span></label>
<label for="nama_kota" class="block text-gray-700 font-medium mb-2">Nama Kabupaten <span class="text-red-500">*</span></label>
<input type="text" id="nama_kota" name="nama_kota" value="{{ old('nama_kota', $kota->nama_kota) }}"
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required>
@error('nama_kota')

View File

@ -61,14 +61,14 @@
</td>
<td class="py-3 px-4 text-sm text-gray-800">
<span class="px-2 py-1 text-xs font-semibold rounded-full
@if($sewa->status === 'completed') bg-green-100 text-green-800
@if($sewa->status === 'selesai') bg-green-100 text-green-800
@elseif($sewa->status === 'confirmed') bg-blue-100 text-blue-800
@else bg-gray-100 text-gray-800 @endif">
{{ ucfirst($sewa->status) }}
</span>
</td>
<td class="py-3 px-4 text-center text-sm">
@if($sewa->status !== 'completed' && $sewa->status !== 'dibatalkan')
@if($sewa->status !== 'selesai' && $sewa->status !== 'dibatalkan')
<form action="{{ route('admin.riwayat.update-status', $sewa->id) }}" method="POST" class="inline">
@csrf
@method('PUT')

View File

@ -33,17 +33,25 @@
</td>
<td>Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}</td>
<td>
<!-- Tombol tandai completed (hanya admin & status confirmed) -->
<!-- Tombol tandai selesai (hanya admin & status confirmed) -->
@if(Auth::user()->tipe_pengguna === 'admin' && $sewa->status === 'confirmed')
<form action="{{ route('riwayat.updateStatus', $sewa->id) }}" method="POST" style="display:inline;">
@csrf
<input type="hidden" name="status" value="completed">
<button type="submit" class="btn btn-success btn-sm" onclick="return confirm('Tandai sebagai LUNAS / completed?')">Tandai Lunas</button>
<input type="hidden" name="status" value="selesai">
<button type="submit" class="btn btn-success btn-sm" onclick="return confirm('Tandai sebagai SELESAI? Ini akan mengaktifkan kembali stok paket.')">Tandai Selesai</button>
</form>
@endif
<!-- Tombol aktifkan stok (hanya admin & status ongoing) -->
@if(Auth::user()->tipe_pengguna === 'admin' && $sewa->status === 'ongoing')
<form action="{{ route('admin.riwayat.activate-stock', $sewa->id) }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="btn btn-warning btn-sm" onclick="return confirm('Aktifkan kembali stok paket? Status sewa akan diubah menjadi selesai.')">Aktifkan Stok</button>
</form>
@endif
<!-- Tombol hapus (jika status sudah selesai atau dibatalkan) -->
@if($sewa->user_id == Auth::id() && in_array($sewa->status, ['completed', 'dibatalkan']))
@if($sewa->user_id == Auth::id() && in_array($sewa->status, ['selesai', 'dibatalkan']))
<form action="{{ url('/riwayat/hapus/'.$sewa->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')

View File

@ -131,10 +131,11 @@ class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">
Setujui
</button>
</form>
<form action="{{ route('admin.verifikasi.reject', $sewa->id) }}" method="POST" class="inline">
<form action="{{ route('admin.verifikasi.reject', $sewa->id) }}" method="POST" class="inline reject-form">
@csrf
<button type="submit"
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
<button type="button"
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600 open-reject-modal"
data-sewa-id="{{ $sewa->id }}">
Tolak
</button>
</form>
@ -162,4 +163,119 @@ class="inline-flex items-center justify-center w-full px-3 py-1 bg-blue-500 text
</div>
@endif
</div>
<!-- Modal Penolakan -->
<div id="rejectModal" class="fixed inset-0 flex items-center justify-center z-50 hidden bg-black bg-opacity-40">
<div class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-4">Pilih Alasan Penolakan</h3>
<form id="rejectReasonForm" method="POST">
@csrf
<input type="hidden" name="sewa_id" id="modalSewaId">
<div class="mb-4">
<select name="alasan_penolakan" id="alasan_penolakan" class="w-full border rounded px-3 py-2">
<option value="">-- Pilih Alasan --</option>
</select>
</div>
<div class="mb-4" id="alasanLainnyaDiv" style="display:none;">
<input type="text"
name="alasan_lainnya"
id="alasan_lainnya"
class="w-full border rounded px-3 py-2"
placeholder="Tulis alasan lain..."
maxlength="255">
</div>
<div class="flex justify-end space-x-2">
<button type="button" id="closeRejectModal" class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">Batal</button>
<button type="submit" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">Tolak</button>
</div>
</form>
</div>
</div>
<script>
// Opsi alasan penolakan fallback jika API tidak tersedia
const FALLBACK_ALASAN = [
'Bukti pembayaran tidak valid',
'Jaminan tidak sesuai',
'Data tidak lengkap',
'Lainnya'
];
// Load alasan penolakan dari server
async function loadAlasanPenolakan() {
try {
const response = await fetch('/admin/verifikasi/alasan-penolakan');
if (!response.ok) {
throw new Error('API tidak tersedia');
}
const data = await response.json();
const select = document.getElementById('alasan_penolakan');
select.innerHTML = '<option value="">-- Pilih Alasan --</option>';
data.alasan_penolakan.forEach(alasan => {
const option = document.createElement('option');
option.value = alasan;
option.textContent = alasan;
select.appendChild(option);
});
} catch (error) {
console.error('Error loading alasan penolakan:', error);
// Gunakan fallback jika API tidak tersedia
const select = document.getElementById('alasan_penolakan');
select.innerHTML = '<option value="">-- Pilih Alasan --</option>';
FALLBACK_ALASAN.forEach(alasan => {
const option = document.createElement('option');
option.value = alasan;
option.textContent = alasan;
select.appendChild(option);
});
}
}
// Load alasan penolakan saat halaman dimuat
document.addEventListener('DOMContentLoaded', loadAlasanPenolakan);
document.querySelectorAll('.open-reject-modal').forEach(btn => {
btn.addEventListener('click', function() {
document.getElementById('rejectModal').classList.remove('hidden');
document.getElementById('modalSewaId').value = this.getAttribute('data-sewa-id');
// Set form action
const sewaId = this.getAttribute('data-sewa-id');
document.getElementById('rejectReasonForm').action = `/admin/verifikasi/${sewaId}/reject`;
});
});
document.getElementById('closeRejectModal').onclick = function() {
document.getElementById('rejectModal').classList.add('hidden');
};
document.getElementById('alasan_penolakan').onchange = function() {
if (this.value === 'Lainnya') {
document.getElementById('alasanLainnyaDiv').style.display = 'block';
} else {
document.getElementById('alasanLainnyaDiv').style.display = 'none';
}
};
document.getElementById('rejectReasonForm').onsubmit = function(e) {
const alasan = document.getElementById('alasan_penolakan').value;
const alasanLainnya = document.getElementById('alasan_lainnya').value;
if (!alasan) {
alert('Silakan pilih alasan penolakan!');
e.preventDefault();
return false;
}
if (alasan === 'Lainnya' && !alasanLainnya.trim()) {
alert('Silakan isi alasan lainnya!');
e.preventDefault();
return false;
}
return true;
};
</script>
@endsection

View File

@ -90,56 +90,36 @@
</div>
</a>
</div>
<!-- Pendapatan Stats -->
<!-- <div class="bg-gradient-to-br from-green-50 to-green-100 rounded-xl shadow-lg p-6 transform hover:scale-105 transition-all duration-300">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-500 text-white mr-4 shadow-md">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<p class="text-green-600 text-sm font-medium">Pendapatan</p>
<p class="text-3xl font-bold text-green-800">
{{ isset($chartData['datasets'][0]['data']) ? 'Rp ' . number_format(end($chartData['datasets'][0]['data']), 0, ',', '.') : 'Rp 0' }}
</p>
<p class="text-sm text-green-600 mt-1">Tahun {{ date('Y') }}</p>
</div>
</div> -->
<!-- </div> -->
</div>
@if(auth()->user()->tipe_pengguna == 'admin')
<!-- Grafik Statistik -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Grafik Pemasukan & Pengeluaran -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-800">Statistik Keuangan & Sewa</h3>
<div class="flex space-x-2">
<span class="px-3 py-1 bg-green-100 text-green-600 rounded-full text-sm font-medium">Pemasukan</span>
<span class="px-3 py-1 bg-red-100 text-red-600 rounded-full text-sm font-medium">Pengeluaran</span>
<span class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm font-medium">Total Sewa</span>
</div>
</div>
<div class="relative h-80">
<canvas id="financeChart"></canvas>
<!-- Grafik Statistik -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Grafik Pemasukan & Pengeluaran -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-800">Statistik Keuangan & Sewa</h3>
<div class="flex space-x-2">
<span class="px-3 py-1 bg-green-100 text-green-600 rounded-full text-sm font-medium">Pemasukan</span>
<span class="px-3 py-1 bg-red-100 text-red-600 rounded-full text-sm font-medium">Pengeluaran</span>
<span class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm font-medium">Total Sewa</span>
</div>
</div>
<!-- Grafik Pertumbuhan Pengguna -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-800">Pertumbuhan Pengguna</h3>
<span class="px-3 py-1 bg-purple-100 text-purple-600 rounded-full text-sm font-medium">Total Pengguna</span>
</div>
<div class="relative h-80">
<canvas id="userGrowthChart"></canvas>
</div>
<div class="relative h-80">
<canvas id="financeChart"></canvas>
</div>
</div>
<!-- Grafik Pertumbuhan Pengguna -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-800">Pertumbuhan Pengguna</h3>
<span class="px-3 py-1 bg-purple-100 text-purple-600 rounded-full text-sm font-medium">Total Pengguna</span>
</div>
<div class="relative h-80">
<canvas id="userGrowthChart"></canvas>
</div>
</div>
</div>
<!-- Ringkasan Statistik -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
@ -165,7 +145,6 @@
Rp {{ isset($chartData['datasets'][0]['data']) ? number_format(end($chartData['datasets'][0]['data']), 0, ',', '.') : '0' }}
</p>
</div>
@endif
<!-- Total Sewa -->
<div class="p-6 bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl border border-blue-200">

View File

@ -19,28 +19,84 @@
<!-- Admin Menu -->
<nav class="mt-4">
<a href="{{ route('admin.dashboard') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('admin.dashboard') ? 'bg-blue-800 text-white' : '' }}">
Dashboard
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z" />
</svg>
Dashboard
</div>
</a>
<a href="{{ route('pengguna') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('pengguna*') ? 'bg-blue-800 text-white' : '' }}">
Manajemen User
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
Manajemen User
</div>
</a>
<a href="{{ route('paket.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('paket*') ? 'bg-blue-800 text-white' : '' }}">
Manajemen Paket
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
Manajemen Paket
</div>
</a>
<a href="{{ route('input-stock') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('input-stock*') ? 'bg-blue-800 text-white' : '' }}">
Manajemen Stock
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
Manajemen Stock
</div>
</a>
<a href="{{ route('sewa.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('sewa.*') ? 'bg-blue-800 text-white' : '' }}">
Manajemen Sewa
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Manajemen Sewa
</div>
</a>
<a href="{{ route('riwayat') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('riwayat') ? 'bg-blue-800 text-white' : '' }}">
Laporan
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Laporan
</div>
</a>
<a href="{{ route('admin.verifikasi.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('admin.verifikasi.*') ? 'bg-blue-800 text-white' : '' }}">
Verifikasi Pembayaran
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Verifikasi Pembayaran
</div>
</a>
<!-- Menu Pesanan Dibatalkan dengan styling khusus -->
<div class="border-t border-gray-200 my-2"></div>
<a href="{{ route('admin.cancelled-orders') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-red-50 hover:text-red-700 {{ request()->routeIs('admin.cancelled-orders*') ? 'bg-red-100 text-red-700 border-r-4 border-red-500' : '' }}">
<div class="flex items-center justify-between">
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
<span>Pesanan Dibatalkan</span>
</div>
<span id="cancelled-orders-badge" class="hidden bg-red-500 text-white text-xs rounded-full px-2 py-1 font-bold">0</span>
</div>
</a>
<a href="{{ route('contact.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('contact.*') ? 'bg-blue-800 text-white' : '' }}">
Kelola Kontak
<div class="flex items-center">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
Kelola Kontak
</div>
</a>
</nav>
</div>
@ -53,6 +109,15 @@
<h1 class="text-xl font-bold text-gray-800">@yield('header', 'Admin Dashboard')</h1>
</div>
<div class="flex items-center space-x-4">
<!-- Notifikasi Pembatalan di Header -->
<a href="{{ route('admin.cancelled-orders') }}" class="relative flex items-center space-x-2 px-3 py-2 bg-red-50 hover:bg-red-100 rounded-lg transition-colors">
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
<span class="text-red-700 font-medium">Pesanan Dibatalkan</span>
<span id="header-cancelled-badge" class="hidden absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full px-2 py-1 font-bold">0</span>
</a>
<a href="{{ route('profile.index') }}" class="font-semibold text-gray-600 flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd" />
@ -97,5 +162,96 @@
</div>
@stack('scripts')
<script>
// Notifikasi real-time untuk pesanan yang dibatalkan
function updateCancelledOrdersBadge() {
fetch('{{ route("admin.cancelled-orders.count") }}')
.then(response => response.json())
.then(data => {
const badge = document.getElementById('cancelled-orders-badge');
if (data.count > 0) {
badge.textContent = data.count;
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
})
.catch(error => {
console.error('Error fetching cancelled orders count:', error);
});
}
// Update badge setiap 30 detik
setInterval(updateCancelledOrdersBadge, 30000);
// Update badge saat halaman dimuat
document.addEventListener('DOMContentLoaded', updateCancelledOrdersBadge);
</script>
<script>
// Notifikasi toast untuk pembatalan pesanan
let lastCancellationCount = 0;
function checkNewCancellations() {
fetch('{{ route("admin.recent-cancellations") }}')
.then(response => response.json())
.then(data => {
if (data.cancellations && data.cancellations.length > 0) {
const latestCancellation = data.cancellations[0];
// Tampilkan toast notifikasi
showToast(
'Pesanan Dibatalkan',
`${latestCancellation.user_name} membatalkan pesanan #${latestCancellation.id} (${latestCancellation.paket_name})`
);
}
})
.catch(error => {
console.error('Error checking cancellations:', error);
});
}
function showToast(title, message) {
document.getElementById('toast-title').textContent = title;
document.getElementById('toast-message').textContent = message;
document.getElementById('toast').classList.remove('hidden');
// Auto hide setelah 5 detik
setTimeout(hideToast, 5000);
}
function hideToast() {
document.getElementById('toast').classList.add('hidden');
}
// Check pembatalan baru setiap 60 detik
setInterval(checkNewCancellations, 60000);
// Check saat halaman dimuat
document.addEventListener('DOMContentLoaded', function() {
setTimeout(checkNewCancellations, 5000); // Check setelah 5 detik
});
</script>
<!-- Toast Notification -->
<div id="toast" class="fixed top-4 right-4 z-50 hidden">
<div class="bg-red-500 text-white px-6 py-4 rounded-lg shadow-lg max-w-sm">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<div>
<h4 class="font-semibold" id="toast-title">Pesanan Dibatalkan</h4>
<p class="text-sm" id="toast-message">Ada pesanan baru yang dibatalkan oleh customer.</p>
</div>
<button onclick="hideToast()" class="ml-4 text-white hover:text-gray-200">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
</div>
</body>
</html>

View File

@ -7,27 +7,41 @@
<!-- Sidebar Menu -->
<nav class="mt-4">
<a href="{{ route('dashboard') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('dashboard') ? 'bg-blue-800 text-white' : '' }}">
<!-- <a href="{{ route('dashboard') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('dashboard') ? 'bg-blue-800 text-white' : '' }}">
Dashboard
</a> -->
@if(auth()->check() && auth()->user()->tipe_pengguna === 'admin')
<a href="{{ route('dashboard') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('dashboard') ? 'bg-blue-800 text-white' : '' }}">
Dashboard
</a>
@if(auth()->user()->tipe_pengguna === 'admin')
<a href="{{ route('pengguna') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('pengguna*') ? 'bg-blue-800 text-white' : '' }}">
pengguna
</a>
<a href="{{ route('input-stock') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('input-stock*') ? 'bg-blue-800 text-white' : '' }}">
Input Stock
</a>
@endif
<a href="{{ route('paket.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('paket*') ? 'bg-blue-800 text-white' : '' }}">
<a href="{{ route('paket.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('paket*') ? 'bg-blue-800 text-white' : '' }}">
Paket
</a>
@if(auth()->user()->tipe_pengguna === 'user')
<!-- Tambahkan menu Pesanan Dibatalkan -->
<!-- <a href="{{ route('admin.cancelled-orders') }}" class="block py-3 px-4 text-red-700 font-semibold mb-1 hover:bg-red-50 hover:text-red-700 {{ request()->routeIs('admin.cancelled-orders*') ? 'bg-red-100 text-red-700 border-r-4 border-red-500' : '' }}">
<span class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
Pesanan Dibatalkan
</span>
</a> -->
@endif
<!-- <a href="{{ route('paket.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('paket*') ? 'bg-blue-800 text-white' : '' }}">
Paket
</a> -->
@if(auth()->check() && auth()->user()->tipe_pengguna === 'user')
<a href="{{ route('sewa.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('sewa.*') ? 'bg-blue-800 text-white' : '' }}">
Sewa
</a>
@endif
@if(auth()->user()->tipe_pengguna === 'admin')
@if(auth()->check() && auth()->user()->tipe_pengguna === 'admin')
<a href="/admin/verifikasi" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->is('admin/verifikasi*') ? 'bg-blue-800 text-white' : '' }}">
Verifikasi
</a>
@ -35,16 +49,16 @@
Laporan Transaksi
</a>
@endif
@if(auth()->user()->tipe_pengguna === 'user')
@if(auth()->check() && auth()->user()->tipe_pengguna === 'user')
<a href="{{ route('riwayat') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('riwayat') ? 'bg-blue-800 text-white' : '' }}">
Riwayat
</a>
@endif
@if(auth()->user()->tipe_pengguna === 'admin')
@if(auth()->check() && auth()->user()->tipe_pengguna === 'admin')
<a href="{{ route('contact.index') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('contact.index') ? 'bg-blue-800 text-white' : '' }}">
Hubungi Kami
</a>
@else
@elseif(auth()->check())
<a href="{{ route('contact.show') }}" class="block py-3 px-4 text-gray-800 font-semibold mb-1 hover:bg-gray-100 {{ request()->routeIs('contact.*') ? 'bg-blue-800 text-white' : '' }}">
Hubungi Kami
</a>

View File

@ -36,16 +36,12 @@ class="w-full h-full object-cover">
</div>
@endif
@php
$activeSewa = $paket->sewas()->whereIn('status', ['confirmed', 'ongoing'])->count();
@endphp
@if($activeSewa > 0)
@if($paket->stok_tersedia <= 0)
<div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="text-white text-center p-4">
<i class="fas fa-clock text-3xl mb-2"></i>
<p class="font-bold">Sedang Disewa</p>
<p class="text-sm">{{ $activeSewa }} dari {{ $paket->stok }} unit disewa</p>
<p class="text-sm">{{ $paket->active_sewa_count }} dari {{ $paket->stok }} unit disewa</p>
</div>
</div>
@endif
@ -70,28 +66,20 @@ class="w-full h-full object-cover">
<span class="text-gray-600">Stok:</span>
<span class="font-semibold">
{{ $paket->stok }}
@if($activeSewa > 0)
<span class="text-orange-500 text-sm ml-1">({{ $paket->stok - $activeSewa }} tersedia)</span>
@if($paket->active_sewa_count > 0)
<span class="text-orange-500 text-sm ml-1">({{ $paket->stok_tersedia }} tersedia)</span>
@endif
</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Status:</span>
<span class="font-semibold
@if($paket->status == 'aktif' && ($paket->stok - $activeSewa) > 0)
@if($paket->is_available)
text-green-600
@else
text-red-600
@endif">
@if($paket->status == 'aktif')
@if(($paket->stok - $activeSewa) > 0)
Aktif ({{ $paket->stok - $activeSewa }} unit tersedia)
@else
Sedang Disewa Semua
@endif
@else
{{ ucfirst($paket->status) }}
@endif
{{ $paket->status_ketersediaan }}
</span>
</div>
@ -118,7 +106,7 @@ class="w-full h-full object-cover">
<!-- Action Buttons - Only for Admin -->
@if(auth()->user()->tipe_pengguna === 'admin')
<div class="flex flex-col space-y-2 mt-4">
@if($activeSewa > 0)
@if($paket->active_sewa_count > 0)
<div class="flex space-x-2">
<form action="{{ route('paket.activate', $paket->id) }}" method="POST" class="flex-1">
@csrf

View File

@ -42,9 +42,6 @@
<th class="py-3 px-4 text-left w-48 border-r">Email</th>
<th class="py-3 px-4 text-left w-36 border-r">No.Telp</th>
<th class="py-3 px-4 text-left border-r">Alamat</th>
@if(auth()->user()->tipe_pengguna === 'admin')
<th class="py-3 px-4 text-center w-24">Aksi</th>
@endif
</tr>
</thead>
<tbody>
@ -56,21 +53,10 @@
<td class="py-3 px-4 border-r">{{ $user->email }}</td>
<td class="py-3 px-4 border-r">{{ $user->no_telp }}</td>
<td class="py-3 px-4 border-r">{{ $user->alamat }}</td>
@if(auth()->user()->tipe_pengguna === 'admin')
<td class="py-3 px-4 text-center">
<form action="{{ route('pengguna.destroy', $user->id) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="bg-red-500 text-white px-4 py-1 rounded-sm font-medium text-sm">
Hapus
</button>
</form>
</td>
@endif
</tr>
@empty
<tr class="border-t">
<td colspan="{{ auth()->user()->tipe_pengguna === 'admin' ? 7 : 6 }}" class="py-3 px-4 text-center">Tidak ada data admin</td>
<td colspan="6" class="py-3 px-4 text-center">Tidak ada data admin</td>
</tr>
@endforelse
</tbody>

View File

@ -51,11 +51,21 @@
<span class="px-2 py-1 rounded-full text-xs font-semibold
@if($sewa->status == 'pending') bg-yellow-100 text-yellow-800
@elseif($sewa->status == 'confirmed') bg-blue-100 text-blue-800
@elseif($sewa->status == 'completed') bg-green-100 text-green-800
@elseif($sewa->status == 'selesai') bg-green-100 text-green-800
@elseif($sewa->status == 'dibatalkan') bg-red-100 text-red-800
@elseif($sewa->status == 'ditolak') bg-red-100 text-red-800
@endif">
{{ ucfirst($sewa->status) }}
</span>
@if($sewa->status == 'ditolak' && $sewa->detail_status)
<div class="mt-1">
<button type="button"
onclick="showAlasanPenolakan('{{ $sewa->detail_status }}')"
class="text-xs text-blue-600 underline">
Lihat Alasan Penolakan
</button>
</div>
@endif
</td>
<td class="py-3 px-4">
<div class="space-y-2">
@ -66,20 +76,15 @@ class="w-full bg-blue-600 text-white px-3 py-1 rounded hover:bg-blue-700">
Detail
</button>
@if(!in_array($sewa->status, ['completed', 'dibatalkan', 'ongoing']))
<form action="{{ route('riwayat.batal', $sewa->id) }}" method="POST">
@csrf
@method('PUT')
<button type="submit"
onclick="return confirm('Apakah Anda yakin ingin membatalkan pesanan ini?' +
(('{{ $sewa->status }}' === 'confirmed') ? ' Pembatalan setelah disetujui mungkin dikenakan biaya pembatalan.' : ''))"
@if(!in_array($sewa->status, ['selesai', 'dibatalkan', 'ongoing']))
<button type="button"
onclick="showCancelForm('{{ $sewa->id }}', '{{ $sewa->paket->nama_paket }}', '{{ $sewa->status }}')"
class="w-full bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
Batalkan Pesanan
</button>
</form>
Batalkan Pesanan
</button>
@endif
@if(in_array($sewa->status, ['dibatalkan', 'completed']))
@if(in_array($sewa->status, ['dibatalkan', 'selesai']))
<form action="{{ route('riwayat.hapus', $sewa->id) }}" method="POST">
@csrf
@method('DELETE')
@ -117,6 +122,113 @@ class="w-full bg-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600">
</div>
</div>
<!-- Modal Alasan Penolakan -->
<div id="alasanPenolakanModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="p-6">
<div class="flex justify-between items-start mb-4">
<h3 class="text-lg font-bold text-gray-800">Alasan Penolakan</h3>
<button onclick="closeAlasanPenolakan()" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
<p id="alasanPenolakanText" class="text-red-700"></p>
</div>
<div class="mt-4 flex justify-end">
<button onclick="closeAlasanPenolakan()"
class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">
Tutup
</button>
</div>
</div>
</div>
</div>
<!-- Modal Form Pembatalan -->
<div id="cancelFormModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="p-6">
<div class="flex justify-between items-start mb-4">
<h3 class="text-lg font-bold text-gray-800">Form Pembatalan Pesanan</h3>
<button onclick="closeCancelForm()" class="text-gray-400 hover:text-gray-600">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mb-4">
<h4 class="font-semibold text-gray-700 mb-2">Detail Pesanan</h4>
<div class="bg-gray-50 p-3 rounded text-sm">
<p><strong>ID Pesanan:</strong> <span id="cancelOrderId"></span></p>
<p><strong>Paket:</strong> <span id="cancelOrderPackage"></span></p>
<p><strong>Status:</strong> <span id="cancelOrderStatus"></span></p>
</div>
</div>
<form id="cancelOrderForm" method="POST">
@csrf
@method('PUT')
<div class="mb-4">
<label for="alasan_pembatalan" class="block text-sm font-medium text-gray-700 mb-2">
Alasan Pembatalan <span class="text-red-500">*</span>
</label>
<select id="alasan_pembatalan" name="alasan_pembatalan"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
required>
<option value="">-- Pilih Alasan --</option>
<option value="Perubahan rencana">Perubahan rencana</option>
<option value="Kendala keuangan">Kendala keuangan</option>
<option value="Tidak sesuai ekspektasi">Tidak sesuai ekspektasi</option>
<option value="Masalah teknis">Masalah teknis</option>
<option value="Lainnya">Lainnya</option>
</select>
</div>
<div class="mb-4" id="alasanLainnyaDiv" style="display:none;">
<label for="alasan_lainnya" class="block text-sm font-medium text-gray-700 mb-2">
Jelaskan alasan lainnya
</label>
<textarea id="alasan_lainnya" name="alasan_lainnya" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
placeholder="Silakan jelaskan alasan pembatalan Anda..."></textarea>
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">Peringatan</h3>
<div class="mt-2 text-sm text-yellow-700">
<p>Dengan membatalkan pesanan ini, Anda tidak akan dapat mengembalikannya. Alasan pembatalan akan dikirim ke admin untuk evaluasi.</p>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-3">
<button type="button" onclick="closeCancelForm()"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Batal
</button>
<button type="submit"
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
Batalkan Pesanan
</button>
</div>
</form>
</div>
</div>
</div>
@else
<div class="text-center py-8">
<p class="text-gray-500">Belum ada riwayat pemesanan</p>
@ -164,6 +276,7 @@ class="w-full bg-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600">
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm">Tanggal Pembayaran: ${data.tanggal_pembayaran || '-'}</p>
<p class="text-sm">Status: ${data.status_pembayaran}</p>
${data.detail_status ? `<p class="text-sm text-red-600">Alasan Penolakan: ${data.detail_status}</p>` : ''}
</div>
</div>
@ -233,6 +346,119 @@ function closeDetail() {
closeDetail();
}
});
function showAlasanPenolakan(alasan) {
const alasanPenolakanText = document.getElementById('alasanPenolakanText');
alasanPenolakanText.textContent = alasan;
document.getElementById('alasanPenolakanModal').classList.remove('hidden');
document.getElementById('alasanPenolakanModal').classList.add('flex');
document.body.style.overflow = 'hidden';
}
function closeAlasanPenolakan() {
document.getElementById('alasanPenolakanModal').classList.add('hidden');
document.getElementById('alasanPenolakanModal').classList.remove('flex');
document.body.style.overflow = 'auto';
}
function showCancelForm(sewaId, paketNama, status) {
document.getElementById('cancelOrderId').textContent = sewaId;
document.getElementById('cancelOrderPackage').textContent = paketNama;
document.getElementById('cancelOrderStatus').textContent = status;
document.getElementById('cancelFormModal').classList.remove('hidden');
document.getElementById('cancelFormModal').classList.add('flex');
document.body.style.overflow = 'hidden';
}
function closeCancelForm() {
document.getElementById('cancelFormModal').classList.add('hidden');
document.getElementById('cancelFormModal').classList.remove('flex');
document.body.style.overflow = 'auto';
}
// Handle alasan pembatalan dropdown
document.addEventListener('DOMContentLoaded', function() {
const alasanSelect = document.getElementById('alasan_pembatalan');
const alasanLainnyaDiv = document.getElementById('alasanLainnyaDiv');
const alasanLainnyaTextarea = document.getElementById('alasan_lainnya');
if (alasanSelect) {
alasanSelect.addEventListener('change', function() {
if (this.value === 'Lainnya') {
alasanLainnyaDiv.style.display = 'block';
alasanLainnyaTextarea.required = true;
} else {
alasanLainnyaDiv.style.display = 'none';
alasanLainnyaTextarea.required = false;
alasanLainnyaTextarea.value = '';
}
});
}
// Handle form submission
const cancelForm = document.getElementById('cancelOrderForm');
if (cancelForm) {
cancelForm.addEventListener('submit', function(e) {
e.preventDefault();
const alasanPembatalan = document.getElementById('alasan_pembatalan').value;
const alasanLainnya = document.getElementById('alasan_lainnya').value;
const sewaId = document.getElementById('cancelOrderId').textContent;
if (!alasanPembatalan) {
alert('Silakan pilih alasan pembatalan!');
return;
}
if (alasanPembatalan === 'Lainnya' && !alasanLainnya.trim()) {
alert('Silakan isi alasan lainnya!');
return;
}
// Gabungkan alasan
const finalAlasan = alasanPembatalan === 'Lainnya' ? alasanLainnya : alasanPembatalan;
// Konfirmasi pembatalan
if (!confirm('Apakah Anda yakin ingin membatalkan pesanan ini? Alasan pembatalan akan dikirim ke admin.')) {
return;
}
// Kirim data ke server
const formData = new FormData();
formData.append('_token', '{{ csrf_token() }}');
formData.append('_method', 'PUT');
formData.append('alasan_pembatalan', finalAlasan);
fetch(`/riwayat/${sewaId}/batal`, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Pesanan berhasil dibatalkan! Alasan pembatalan telah dikirim ke admin.');
window.location.reload();
} else {
alert('Terjadi kesalahan: ' + (data.message || 'Gagal membatalkan pesanan'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Terjadi kesalahan saat membatalkan pesanan. Silakan coba lagi.');
});
});
}
});
// Menutup modal saat mengklik area di luar modal
document.getElementById('cancelFormModal').addEventListener('click', function(e) {
if (e.target === this) {
closeCancelForm();
}
});
</script>
@endpush
@endsection

View File

@ -0,0 +1,87 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold mb-6 text-gray-800">Form Pembatalan Pesanan</h2>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3 text-gray-700">Detail Pesanan</h3>
<div class="bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-sm text-gray-600">ID Pesanan:</p>
<p class="font-medium">{{ $sewa->id }}</p>
</div>
<div>
<p class="text-sm text-gray-600">Status:</p>
<p class="font-medium">{{ ucfirst($sewa->status) }}</p>
</div>
<div>
<p class="text-sm text-gray-600">Paket:</p>
<p class="font-medium">{{ $sewa->paket->nama ?? 'N/A' }}</p>
</div>
<div>
<p class="text-sm text-gray-600">Tanggal Sewa:</p>
<p class="font-medium">{{ $sewa->created_at->format('d/m/Y H:i') }}</p>
</div>
</div>
</div>
</div>
<form action="{{ route('riwayat.process-cancel', $sewa->id) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-6">
<label for="alasan_pembatalan" class="block text-sm font-medium text-gray-700 mb-2">
Alasan Pembatalan <span class="text-red-500">*</span>
</label>
<textarea
id="alasan_pembatalan"
name="alasan_pembatalan"
rows="4"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent @error('alasan_pembatalan') border-red-500 @enderror"
placeholder="Silakan jelaskan alasan pembatalan pesanan Anda..."
required
>{{ old('alasan_pembatalan') }}</textarea>
@error('alasan_pembatalan')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">
Peringatan
</h3>
<div class="mt-2 text-sm text-yellow-700">
<p>Dengan membatalkan pesanan ini, Anda tidak akan dapat mengembalikannya. Pastikan Anda yakin dengan keputusan ini.</p>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-4">
<a href="{{ route('riwayat') }}"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Kembali
</a>
<button type="submit"
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
onclick="return confirm('Apakah Anda yakin ingin membatalkan pesanan ini?')">
Batalkan Pesanan
</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -27,19 +27,12 @@ class="w-full h-full object-cover">
</div>
@endif
@php
$activeSewa = $paket->sewas()
->whereIn('status', ['confirmed', 'ongoing'])
->count();
$tersedia = $paket->stok - $activeSewa;
@endphp
@if($tersedia <= 0)
@if($paket->stok_tersedia <= 0)
<div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="text-white text-center p-4">
<i class="fas fa-clock text-3xl mb-2"></i>
<p class="font-bold">Sedang Disewa</p>
<p class="text-sm">{{ $activeSewa }} dari {{ $paket->stok }} unit disewa</p>
<p class="text-sm">{{ $paket->active_sewa_count }} dari {{ $paket->stok }} unit disewa</p>
</div>
</div>
@endif
@ -64,24 +57,20 @@ class="w-full h-full object-cover">
<span class="text-gray-600">Stok:</span>
<span class="font-semibold">
{{ $paket->stok }}
@if($activeSewa > 0)
<span class="text-orange-500 text-sm ml-1">({{ $tersedia }} tersedia)</span>
@if($paket->active_sewa_count > 0)
<span class="text-orange-500 text-sm ml-1">({{ $paket->stok_tersedia }} tersedia)</span>
@endif
</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Status:</span>
<span class="font-semibold
@if($tersedia > 0)
@if($paket->is_available)
text-green-600
@else
text-red-600
@endif">
@if($tersedia > 0)
Tersedia ({{ $tersedia }} unit)
@else
Sedang Disewa Semua
@endif
{{ $paket->status_ketersediaan }}
</span>
</div>
@ -114,7 +103,7 @@ class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-semibold py-2 px-
</button>
@auth
@if($tersedia <= 0)
@if($paket->stok_tersedia <= 0)
<button disabled
class="w-full bg-gray-400 text-white py-2 px-4 rounded cursor-not-allowed">
<i class="fas fa-ban mr-2"></i>Tidak Tersedia

View File

@ -78,11 +78,11 @@ class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:r
<!-- Kota Tujuan -->
<div>
<label for="kota_id" class="block text-sm font-medium text-gray-700 mb-2">Kota Tujuan</label>
<label for="kota_id" class="block text-sm font-medium text-gray-700 mb-2">Kabupaten Tujuan</label>
<select id="kota_id" name="kota_id"
class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200"
required>
<option value="">Pilih Kota</option>
<option value="">Pilih Kabupaten</option>
@foreach($paket->ongkirKota as $kota)
<option value="{{ $kota->id }}" data-ongkir="{{ $kota->biaya_ongkir }}">
{{ $kota->nama_kota }} - Rp {{ number_format($kota->biaya_ongkir, 0, ',', '.') }}

View File

@ -70,6 +70,9 @@
@endif">
{{ ucfirst($sewa->status) }}
</span>
@if($sewa->status === 'ditolak' && $sewa->catatan)
<button type="button" class="ml-2 text-xs text-blue-600 underline lihat-penolakan-btn" data-alasan="{{ $sewa->catatan }}">Lihat Penolakan</button>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}
@ -79,6 +82,10 @@
class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-sm">
Detail
</button>
@if($sewa->status === 'ditolak' && $sewa->catatan)
<button type="button" class="mt-2 block text-xs text-blue-600 underline lihat-penolakan-btn" data-alasan="{{ $sewa->catatan }}">Lihat Penolakan</button>
@endif
<button class="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded-md text-sm mt-2">Batalkan Pesanan</button>
</td>
</tr>
@empty
@ -112,57 +119,57 @@ class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-sm">
</div>
</div>
@push('scripts')
<script>
function showDetail(sewaId) {
fetch(`/sewa/${sewaId}/detail`)
.then(response => response.json())
.then(data => {
const modal = document.getElementById('detailModal');
const content = document.getElementById('modalContent');
<!-- Modal Alasan Penolakan -->
<div id="modalPenolakan" class="fixed inset-0 flex items-center justify-center z-50 hidden bg-black bg-opacity-40">
<div class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-4">Alasan Penolakan</h3>
<div id="alasanPenolakanText" class="mb-4 text-gray-700"></div>
<div class="flex justify-end">
<button type="button" id="closeModalPenolakan" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Tutup</button>
</div>
</div>
</div>
content.innerHTML = `
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
<div>
<h4 class="text-sm font-medium text-gray-500">Informasi Penyewa</h4>
<p class="text-gray-900">${data.user.nama}</p>
<p class="text-gray-600">${data.user.email}</p>
<p class="text-gray-600">${data.user.no_hp || '-'}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Paket Disewa</h4>
<p class="text-gray-900">${data.paket.nama_paket}</p>
<p class="text-gray-600">${data.paket.jenis_paket}</p>
</div>
</div>
<div class="space-y-3">
<div>
<h4 class="text-sm font-medium text-gray-500">Detail Sewa</h4>
<p class="text-gray-600">Mulai: ${data.tanggal_mulai}</p>
<p class="text-gray-600">Selesai: ${data.tanggal_selesai}</p>
<p class="text-gray-600">Status: ${data.status}</p>
</div>
<div>
<h4 class="text-sm font-medium text-gray-500">Pembayaran</h4>
<p class="text-gray-900">Total: Rp ${data.total_harga}</p>
<p class="text-gray-600">Dibayar: Rp ${data.nominal_pembayaran || 0}</p>
</div>
</div>
<div class="col-span-2">
<h4 class="text-sm font-medium text-gray-500">Lokasi Pengiriman</h4>
<p class="text-gray-900">${data.lokasi}</p>
<p class="text-gray-600">Kota: ${data.kota ? data.kota.nama_kota : '-'}</p>
</div>
<script>
function showDetail(id) {
fetch(`/sewa/${id}/json`)
.then(response => {
if (!response.ok) throw new Error('Gagal mengambil data');
return response.json();
})
.then(sewa => {
let barangList = '';
if (sewa.barangs && sewa.barangs.length > 0) {
barangList = '<ul>';
sewa.barangs.forEach(barang => {
barangList += `<li>${barang.nama_barang} (${barang.jumlah} unit)</li>`;
});
barangList += '</ul>';
} else {
barangList = '<p>Tidak ada barang dalam paket</p>';
}
document.getElementById('modalContent').innerHTML = `
<div class="space-y-2">
<p><b>Nama Paket:</b> ${sewa.paket.nama_paket}</p>
<p><b>Jenis Paket:</b> ${sewa.paket.jenis_paket}</p>
<p><b>Harga per Hari:</b> Rp ${parseInt(sewa.paket.harga).toLocaleString('id-ID')}</p>
<p><b>Penyewa:</b> ${sewa.user.nama} (${sewa.user.email})</p>
<p><b>Tanggal Sewa:</b> ${sewa.tanggal_mulai} - ${sewa.tanggal_selesai}</p>
<p><b>Status:</b> ${sewa.status}</p>
<p><b>Total Harga:</b> Rp ${parseInt(sewa.total_harga).toLocaleString('id-ID')}</p>
<p><b>Kota:</b> ${sewa.kota || '-'}</p>
<p><b>Alamat:</b> ${sewa.lokasi || '-'}</p>
${sewa.catatan ? `<p><b>Catatan:</b> ${sewa.catatan}</p>` : ''}
<b>Daftar Barang:</b>
${barangList}
</div>
`;
const modal = document.getElementById('detailModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
})
.catch(error => {
console.error('Error:', error);
alert('Terjadi kesalahan saat memuat detail');
.catch(() => {
alert('Gagal memuat detail sewa');
});
}
@ -178,6 +185,16 @@ function closeModal() {
closeModal();
}
});
document.querySelectorAll('.lihat-penolakan-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.getElementById('alasanPenolakanText').innerText = this.getAttribute('data-alasan');
document.getElementById('modalPenolakan').classList.remove('hidden');
});
});
document.getElementById('closeModalPenolakan').onclick = function() {
document.getElementById('modalPenolakan').classList.add('hidden');
};
</script>
@endpush
@endsection

View File

@ -62,13 +62,16 @@
bg-green-100 text-green-800
@elseif($sewa->status === 'ongoing')
bg-blue-100 text-blue-800
@elseif($sewa->status === 'completed')
@elseif($sewa->status === 'selesai')
bg-purple-100 text-purple-800
@else
bg-red-100 text-red-800
@endif">
{{ ucfirst($sewa->status) }}
</span>
@if($sewa->status === 'ditolak' && $sewa->catatan)
<button type="button" class="ml-2 text-xs text-blue-600 underline lihat-penolakan-btn" data-alasan="{{ $sewa->catatan }}">Lihat Penolakan</button>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}
@ -92,4 +95,26 @@ class="text-blue-600 hover:text-blue-900">
</div>
</div>
</div>
<!-- Modal Alasan Penolakan -->
<div id="modalPenolakan" class="fixed inset-0 flex items-center justify-center z-50 hidden bg-black bg-opacity-40">
<div class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md">
<h3 class="text-lg font-bold mb-4">Alasan Penolakan</h3>
<div id="alasanPenolakanText" class="mb-4 text-gray-700"></div>
<div class="flex justify-end">
<button type="button" id="closeModalPenolakan" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Tutup</button>
</div>
</div>
</div>
<script>
document.querySelectorAll('.lihat-penolakan-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.getElementById('alasanPenolakanText').innerText = this.getAttribute('data-alasan');
document.getElementById('modalPenolakan').classList.remove('hidden');
});
});
document.getElementById('closeModalPenolakan').onclick = function() {
document.getElementById('modalPenolakan').classList.add('hidden');
};
</script>
@endsection

View File

@ -117,7 +117,7 @@
<div class="bg-blue-50 border-l-4 border-blue-500 text-blue-700 p-4 mb-4">
<p class="font-medium">Informasi Pembayaran:</p>
<p>tolong masukkan nominal pembayaran sebesar
DP50% dari total harga yang sudah diberikan
DP30% sampai 100% dari total harga yang sudah diberikan
segera lakukan pembayaran ke rekening dibawah ini
agar proses penyewaan ditangani lebih cepat
jika ada masalah mohon hubungi admin di bagian hubungi kami
@ -130,18 +130,22 @@
<!-- Nominal Pembayaran -->
<div>
<label for="nominal_pembayaran" class="block text-sm font-medium text-gray-700 mb-1">
Nominal Pembayaran (DP 50% = Rp {{ number_format($sewa->total_harga * 0.5, 0, ',', '.') }})
Nominal Pembayaran (Pilih persentase pembayaran)
</label>
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="text-gray-500 sm:text-sm">Rp</span>
</div>
<input type="number" name="nominal_pembayaran" id="nominal_pembayaran"
<select name="nominal_pembayaran" id="nominal_pembayaran"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
placeholder="Masukkan nominal pembayaran sesuai keinginan"
min="1"
max="{{ $sewa->total_harga }}"
required>
<option value="">Pilih nominal pembayaran</option>
@for ($i = 30; $i <= 100; $i += 10)
<option value="{{ intval($sewa->total_harga * ($i/100)) }}">
{{ $i }}% = Rp {{ number_format($sewa->total_harga * ($i/100), 0, ',', '.') }}
</option>
@endfor
</select>
</div>
@error('nominal_pembayaran')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>

View File

@ -81,6 +81,10 @@
Route::post('/pengguna', [PenggunaController::class, 'store'])->name('pengguna.store');
Route::delete('/pengguna/{id}', [PenggunaController::class, 'destroy'])->name('pengguna.destroy');
// Users (data pengguna biasa)
Route::get('/users', [UserController::class, 'index'])->name('users');
Route::delete('/users/{id}', [UserController::class, 'destroy'])->name('users.destroy');
// Admin
Route::get('/admin', [AdminController::class, 'index'])->name('admin');
Route::get('/admin/tambah', [AdminController::class, 'create'])->name('admin.create');
@ -156,11 +160,21 @@
Route::delete('/riwayat/hapus/{id}', [\App\Http\Controllers\RiwayatController::class, 'hapus']);
Route::get('/riwayat', [\App\Http\Controllers\RiwayatController::class, 'index'])->name('riwayat');
// Route untuk mengaktifkan stok dari riwayat
Route::post('/riwayat/{id}/activate-stock', [\App\Http\Controllers\RiwayatController::class, 'activateStock'])->name('riwayat.activate-stock');
// Route untuk pesanan yang dibatalkan
Route::get('/cancelled-orders', [App\Http\Controllers\Admin\DashboardController::class, 'cancelledOrders'])->name('cancelled-orders');
Route::get('/cancelled-orders/{id}', [App\Http\Controllers\Admin\DashboardController::class, 'showCancelledOrder'])->name('cancelled-orders.show');
Route::get('/cancelled-orders-count', [App\Http\Controllers\Admin\DashboardController::class, 'getCancelledOrdersCount'])->name('cancelled-orders.count');
Route::get('/recent-cancellations', [App\Http\Controllers\Admin\DashboardController::class, 'getRecentCancellations'])->name('recent-cancellations');
// Verifikasi Pembayaran
Route::get('/verifikasi', [App\Http\Controllers\Admin\VerifikasiController::class, 'index'])->name('verifikasi.index');
Route::post('/verifikasi/{id}/approve', [App\Http\Controllers\Admin\VerifikasiController::class, 'approve'])->name('verifikasi.approve');
Route::post('/verifikasi/{id}/reject', [App\Http\Controllers\Admin\VerifikasiController::class, 'reject'])->name('verifikasi.reject');
Route::get('/verifikasi/alasan-penolakan', [App\Http\Controllers\Admin\VerifikasiController::class, 'getAlasanPenolakan'])->name('verifikasi.alasan-penolakan');
// Laporan Transaksi
Route::get('/reports/transactions', [App\Http\Controllers\Admin\ReportController::class, 'transactions'])->name('reports.transactions');
@ -217,8 +231,4 @@
Route::get('/paket/{id}/detail', [PaketController::class, 'detail'])->name('paket.detail');
// Route untuk data pengguna biasa
Route::get('/users', [UserController::class, 'index'])->name('users');
Route::delete('/users/{id}', [UserController::class, 'destroy'])->name('users.destroy');
Route::put('/admin/riwayat/{id}/update-status', [App\Http\Controllers\Admin\ReportController::class, 'updateStatus'])->name('admin.riwayat.update-status');