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\Paket;
use App\Models\Sewa; use App\Models\Sewa;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
class DashboardController extends Controller class DashboardController extends Controller
{ {
@ -17,14 +18,22 @@ public function index()
$stats = [ $stats = [
'users' => User::where('role', 'customer')->count(), 'users' => User::where('role', 'customer')->count(),
'packages' => Paket::count(), 'packages' => Paket::count(),
'rentals' => Sewa::count(), 'rentals' => Sewa::whereIn('status', ['disetujui', 'selesai'])->count(),
'revenue' => Sewa::where('status', 'selesai')->sum('total_harga') 'revenue' => Sewa::where('status', 'selesai')->sum('total_harga')
]; ];
// Mengambil aktivitas terbaru // Mengambil aktivitas terbaru
$activities = $this->getRecentActivities(); $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() private function getRecentActivities()
@ -33,6 +42,7 @@ private function getRecentActivities()
// Aktivitas sewa terbaru // Aktivitas sewa terbaru
$recentRentals = Sewa::with(['user', 'paket']) $recentRentals = Sewa::with(['user', 'paket'])
->whereIn('status', ['disetujui', 'selesai'])
->latest() ->latest()
->take(5) ->take(5)
->get(); ->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 // Urutkan berdasarkan waktu terbaru
usort($activities, function($a, $b) { usort($activities, function($a, $b) {
return strtotime($b['time']) - strtotime($a['time']); return strtotime($b['time']) - strtotime($a['time']);
@ -70,4 +97,86 @@ private function getRecentActivities()
return array_slice($activities, 0, 5); 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 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 * Menampilkan daftar pembayaran yang perlu diverifikasi
*/ */
@ -51,7 +59,7 @@ public function approve($id)
/** /**
* Menolak pembayaran * Menolak pembayaran
*/ */
public function reject($id) public function reject(Request $request, $id)
{ {
try { try {
$sewa = Sewa::findOrFail($id); $sewa = Sewa::findOrFail($id);
@ -61,9 +69,32 @@ public function reject($id)
return back()->with('error', 'Status sewa tidak valid untuk ditolak.'); 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([ $sewa->update([
'status' => 'ditolak' 'status' => 'ditolak',
'detail_status' => $alasan,
'catatan' => 'Pembayaran ditolak: ' . $alasan
]); ]);
return back()->with('success', 'Pembayaran telah ditolak.'); return back()->with('success', 'Pembayaran telah ditolak.');
@ -71,4 +102,14 @@ public function reject($id)
return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); 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) 'active_rentals' => Sewa::where('user_id', $user->id)
->whereIn('status', ['menunggu', 'diproses']) ->whereIn('status', ['menunggu', 'diproses'])
->count(), ->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) 'total_spent' => Sewa::where('user_id', $user->id)
->where('status', 'selesai') ->where('status', 'selesai')
->sum('total_harga') ->sum('total_harga')
@ -34,6 +36,7 @@ private function getRecentRentals($userId)
{ {
$rentals = Sewa::with('paket') $rentals = Sewa::with('paket')
->where('user_id', $userId) ->where('user_id', $userId)
->whereIn('status', ['disetujui', 'selesai'])
->latest() ->latest()
->take(5) ->take(5)
->get(); ->get();

View File

@ -22,21 +22,26 @@ public function index()
// Cek tipe pengguna yang sedang login // Cek tipe pengguna yang sedang login
$userType = Auth::user()->tipe_pengguna; $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 = [ $stats = [
'pengguna' => User::where('tipe_pengguna', 'user')->count(), 'pengguna' => User::where('tipe_pengguna', 'user')->count(),
'admin' => User::where('tipe_pengguna', 'admin')->count(), 'admin' => User::where('tipe_pengguna', 'admin')->count(),
'total_pengguna' => User::count(), 'total_pengguna' => User::count(),
'barang' => Barang::count(), 'barang' => Barang::count(),
'sewa' => DB::table('sewas')->count() 'sewa' => DB::table('sewas')->whereIn('status', ['disetujui', 'selesai'])->count()
]; ];
// Data grafik per tahun // Data grafik per tahun
$yearlyStats = DB::table('sewas') $yearlyStats = DB::table('sewas')
->select( ->select(
DB::raw('YEAR(created_at) as tahun'), DB::raw('YEAR(created_at) as tahun'),
DB::raw('COUNT(*) as total_sewa'), DB::raw('COUNT(CASE WHEN status IN ("disetujui", "selesai") THEN 1 END) as total_sewa'),
DB::raw('SUM(total_harga) as total_pemasukan'), 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 DB::raw('0 as total_pengeluaran') // Sementara set 0 karena belum ada kolom biaya_operasional
) )
->groupBy('tahun') ->groupBy('tahun')
@ -121,63 +126,35 @@ public function index()
] ]
]; ];
// Debug data // Data aktivitas (hanya untuk admin)
// dd($chartData, $userChartData);
// Data aktivitas
$aktivitas = []; $aktivitas = [];
// Jika user adalah admin, tampilkan semua data // Ambil user terbaru yang mendaftar (baik admin maupun user)
if ($userType === 'admin') { $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) { 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
$aktivitas[] = [ $aktivitas[] = [
'type' => 'info', 'type' => 'pengguna',
'title' => 'Selamat Datang', 'title' => 'Pengguna ' . ucfirst($user->tipe_pengguna),
'description' => 'Anda login sebagai pengguna biasa. Anda hanya dapat melihat informasi.', 'description' => $user->nama . ' (' . $user->tipe_pengguna . ')',
'time' => 'Saat ini', 'time' => Carbon::parse($user->created_at)->diffForHumans(),
'icon_color' => 'blue' 'icon_color' => $user->tipe_pengguna === 'admin' ? 'indigo' : 'blue'
]; ];
}
// Tampilkan riwayat sewa pengguna ini saja // Tambahkan aktivitas sewa terbaru
$userSewa = Sewa::where('user_id', Auth::id()) $latestSewa = Sewa::with(['user', 'paket'])
->with('paket') ->whereIn('status', ['disetujui', 'selesai'])
->latest() ->latest()
->first(); ->first();
if ($latestSewa) {
if ($userSewa) { $aktivitas[] = [
$aktivitas[] = [ 'type' => 'sewa',
'type' => 'sewa', 'title' => 'Pemesanan Baru',
'title' => 'Pesanan Terakhir Anda', 'description' => $latestSewa->user->nama . ' memesan paket ' . $latestSewa->paket->nama_paket,
'description' => 'Paket: ' . $userSewa->paket->nama_paket, 'time' => Carbon::parse($latestSewa->created_at)->diffForHumans(),
'time' => Carbon::parse($userSewa->created_at)->diffForHumans(), 'icon_color' => 'green'
'icon_color' => 'green' ];
];
}
} }
return view('dashboard', compact('stats', 'chartData', 'userChartData', 'aktivitas', 'userType')); return view('dashboard', compact('stats', 'chartData', 'userChartData', 'aktivitas', 'userType'));

View File

@ -209,7 +209,7 @@ public function update(Request $request, $id)
} }
DB::commit(); DB::commit();
return redirect()->route('paket')->with('success', 'Paket berhasil diperbarui!'); return redirect()->route('paket.index')->with('success', 'Paket berhasil diperbarui!');
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollback(); DB::rollback();
@ -402,17 +402,17 @@ public function activate($id)
->first(); ->first();
if (!$oldestSewa) { 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 // Update status sewa menjadi selesai hanya untuk sewa terlama
$oldestSewa->update(['status' => 'completed']); $oldestSewa->update(['status' => 'selesai']);
DB::commit(); 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) { } catch (\Exception $e) {
DB::rollback(); 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); $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() $count = $paket->sewas()
->whereIn('status', ['confirmed', 'ongoing']) ->whereIn('status', ['confirmed', 'ongoing'])
->update(['status' => 'completed']); ->update(['status' => 'selesai']);
if ($count === 0) { 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(); 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) { } catch (\Exception $e) {
DB::rollback(); 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() public function __construct()
{ {
// Middleware untuk memastikan user sudah login
$this->middleware('auth');
// Middleware untuk method yang membutuhkan akses admin // Middleware untuk method yang membutuhkan akses admin
$this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']); $this->middleware('admin')->only(['create', 'store', 'edit', 'update', 'destroy']);
} }

View File

@ -7,6 +7,8 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Log;
class RiwayatController extends Controller class RiwayatController extends Controller
{ {
@ -21,6 +23,23 @@ public function index()
return view('riwayat', compact('sewas')); 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) public function updateStatus(Request $request, $id)
{ {
if (Auth::user()->tipe_pengguna !== 'admin') { if (Auth::user()->tipe_pengguna !== 'admin') {
@ -28,7 +47,7 @@ public function updateStatus(Request $request, $id)
} }
$request->validate([ $request->validate([
'status' => 'required|in:completed' 'status' => 'required|in:selesai'
]); ]);
$sewa = Sewa::findOrFail($id); $sewa = Sewa::findOrFail($id);
@ -40,7 +59,7 @@ public function updateStatus(Request $request, $id)
$sewa->status = $request->status; $sewa->status = $request->status;
$sewa->save(); $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) public function hapus($id)
@ -52,7 +71,7 @@ public function hapus($id)
->with('error', 'Anda tidak memiliki akses untuk menghapus pesanan ini.'); ->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') return redirect()->route('riwayat')
->with('error', 'Hanya pesanan yang sudah selesai atau dibatalkan yang dapat dihapus.'); ->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.'); ->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) public function show($id)
{ {
try {
$sewa = Sewa::with(['paket', 'kota'])->findOrFail($id); $sewa = Sewa::with(['paket', 'kota'])->findOrFail($id);
// Pastikan hanya user yang berhak yang bisa akses // Pastikan hanya user yang berhak yang bisa akses
@ -79,18 +125,206 @@ public function show($id)
return response()->json(['error' => 'Unauthorized'], 403); 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([ return response()->json([
'tanggal_pembayaran' => $sewa->tanggal_pembayaran, 'tanggal_pembayaran' => $sewa->tanggal_pembayaran,
'status_pembayaran' => $sewa->status, 'status_pembayaran' => $sewa->status,
'detail_status' => $sewa->detail_status,
'lokasi' => $sewa->lokasi, 'lokasi' => $sewa->lokasi,
'kota' => $sewa->kota, 'kota' => $sewa->kota ?? null,
'ongkir' => $sewa->ongkir, 'ongkir' => $sewa->ongkir,
'bukti_pembayaran' => $sewa->bukti_pembayaran, 'bukti_pembayaran' => $sewa->bukti_pembayaran,
'foto_jaminan' => $sewa->foto_jaminan, 'foto_jaminan' => $sewa->foto_jaminan,
'jenis_jaminan' => $sewa->jenis_jaminan, 'jenis_jaminan' => $sewa->jenis_jaminan,
'nomor_rekening' => $nomor_rekening, '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); $paket = Paket::with('ongkirKota')->findOrFail($id);
// Cek apakah paket tersedia // Cek apakah paket tersedia
$activeSewa = $paket->sewas() if (!$paket->is_available) {
->whereIn('status', ['confirmed', 'ongoing'])
->count();
if ($activeSewa >= $paket->stok) {
return redirect()->route('sewa.index') return redirect()->route('sewa.index')
->with('error', 'Maaf, paket ini sedang tidak tersedia.'); ->with('error', 'Maaf, paket ini sedang tidak tersedia.');
} }
@ -89,12 +85,16 @@ public function store(Request $request)
try { try {
$paket = Paket::findOrFail($request->paket_id); $paket = Paket::findOrFail($request->paket_id);
// Cek ketersediaan paket // Cek ketersediaan paket untuk tanggal yang dipilih
$activeSewa = $paket->sewas() $activeSewa = $paket->sewas()
->whereIn('status', ['confirmed', 'ongoing']) ->whereIn('status', ['confirmed', 'ongoing'])
->where(function($query) use ($request) { ->where(function($query) use ($request) {
$query->whereBetween('tanggal_mulai', [$request->tanggal_mulai, $request->tanggal_selesai]) $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(); ->count();
@ -309,4 +309,34 @@ public function detail($id)
], 500); ], 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() public function __construct()
{ {
// Middleware untuk memastikan user sudah login
$this->middleware('auth');
// Middleware hanya untuk admin yang mengakses destroy // Middleware hanya untuk admin yang mengakses destroy
$this->middleware('admin')->only(['destroy']); $this->middleware('admin')->only(['destroy']);
} }

View File

@ -123,4 +123,46 @@ public function sewas()
{ {
return $this->hasMany(Sewa::class); 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', 'catatan',
'total_harga', 'total_harga',
'status', 'status',
'detail_status',
'bukti_pembayaran', 'bukti_pembayaran',
'tanggal_pembayaran', 'tanggal_pembayaran',
'jenis_jaminan', 'jenis_jaminan',
@ -92,7 +93,7 @@ public function scopeStatus($query, $status)
// Scope untuk sewa yang aktif // Scope untuk sewa yang aktif
public function scopeAktif($query) public function scopeAktif($query)
{ {
return $query->whereIn('status', ['disetujui']) return $query->whereIn('status', ['confirmed'])
->where('tanggal_selesai', '>=', now()); ->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 --> <!-- Ongkir per Kota -->
<div class="mb-4"> <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 id="ongkir-container">
<div class="ongkir-form mb-2 p-4 border rounded-lg bg-gray-50"> <div class="ongkir-form mb-2 p-4 border rounded-lg bg-gray-50">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <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"> <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>
<div> <div>
@ -110,7 +110,7 @@
</div> </div>
</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"> <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> </button>
</div> </div>
</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> </div>
@endif @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"> <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> <h2 class="text-lg font-semibold mb-4">Pengaturan Nomor Rekening</h2>
<form action="{{ route('admin.save-rekening') }}" method="POST"> <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="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 class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label class="block text-gray-700 font-medium mb-2">Nama Kota <span class="text-red-500">*</span></label> <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_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> <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>
<div> <div>
<label class="block text-gray-700 font-medium mb-2">Biaya Ongkir (Rp) <span class="text-red-500">*</span></label> <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 class="space-y-6">
<div> <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) }}" <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> 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') @error('nama_kota')

View File

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

View File

@ -33,17 +33,25 @@
</td> </td>
<td>Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}</td> <td>Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}</td>
<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') @if(Auth::user()->tipe_pengguna === 'admin' && $sewa->status === 'confirmed')
<form action="{{ route('riwayat.updateStatus', $sewa->id) }}" method="POST" style="display:inline;"> <form action="{{ route('riwayat.updateStatus', $sewa->id) }}" method="POST" style="display:inline;">
@csrf @csrf
<input type="hidden" name="status" value="completed"> <input type="hidden" name="status" value="selesai">
<button type="submit" class="btn btn-success btn-sm" onclick="return confirm('Tandai sebagai LUNAS / completed?')">Tandai Lunas</button> <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> </form>
@endif @endif
<!-- Tombol hapus (jika status sudah selesai atau dibatalkan) --> <!-- 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;"> <form action="{{ url('/riwayat/hapus/'.$sewa->id) }}" method="POST" style="display:inline;">
@csrf @csrf
@method('DELETE') @method('DELETE')

View File

@ -131,10 +131,11 @@ class="bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600">
Setujui Setujui
</button> </button>
</form> </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 @csrf
<button type="submit" <button type="button"
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600"> class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600 open-reject-modal"
data-sewa-id="{{ $sewa->id }}">
Tolak Tolak
</button> </button>
</form> </form>
@ -162,4 +163,119 @@ class="inline-flex items-center justify-center w-full px-3 py-1 bg-blue-500 text
</div> </div>
@endif @endif
</div> </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 @endsection

View File

@ -90,56 +90,36 @@
</div> </div>
</a> </a>
</div> </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> </div>
@if(auth()->user()->tipe_pengguna == 'admin') <!-- Grafik Statistik -->
<!-- Grafik Statistik --> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> <!-- Grafik Pemasukan & Pengeluaran -->
<!-- Grafik Pemasukan & Pengeluaran --> <div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300">
<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">
<div class="flex items-center justify-between mb-6"> <h3 class="text-xl font-bold text-gray-800">Statistik Keuangan & Sewa</h3>
<h3 class="text-xl font-bold text-gray-800">Statistik Keuangan & Sewa</h3> <div class="flex space-x-2">
<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-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-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>
<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>
</div> </div>
</div> </div>
<div class="relative h-80">
<!-- Grafik Pertumbuhan Pengguna --> <canvas id="financeChart"></canvas>
<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>
</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 --> <!-- Ringkasan Statistik -->
<div class="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow duration-300"> <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' }} Rp {{ isset($chartData['datasets'][0]['data']) ? number_format(end($chartData['datasets'][0]['data']), 0, ',', '.') : '0' }}
</p> </p>
</div> </div>
@endif
<!-- Total Sewa --> <!-- Total Sewa -->
<div class="p-6 bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl border border-blue-200"> <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 --> <!-- Admin Menu -->
<nav class="mt-4"> <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' : '' }}"> <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>
<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' : '' }}"> <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>
<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' : '' }}">
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>
<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' : '' }}"> <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>
<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' : '' }}"> <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>
<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' : '' }}"> <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>
<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' : '' }}"> <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> </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' : '' }}"> <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> </a>
</nav> </nav>
</div> </div>
@ -53,6 +109,15 @@
<h1 class="text-xl font-bold text-gray-800">@yield('header', 'Admin Dashboard')</h1> <h1 class="text-xl font-bold text-gray-800">@yield('header', 'Admin Dashboard')</h1>
</div> </div>
<div class="flex items-center space-x-4"> <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"> <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"> <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" /> <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> </div>
@stack('scripts') @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> </body>
</html> </html>

View File

@ -7,27 +7,41 @@
<!-- Sidebar Menu --> <!-- Sidebar Menu -->
<nav class="mt-4"> <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 Dashboard
</a> </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' : '' }}"> <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 pengguna
</a> </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' : '' }}"> <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 Input Stock
</a> </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 Paket
</a> </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' : '' }}"> <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 Sewa
</a> </a>
@endif @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' : '' }}"> <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 Verifikasi
</a> </a>
@ -35,16 +49,16 @@
Laporan Transaksi Laporan Transaksi
</a> </a>
@endif @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' : '' }}"> <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 Riwayat
</a> </a>
@endif @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' : '' }}"> <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 Hubungi Kami
</a> </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' : '' }}"> <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 Hubungi Kami
</a> </a>

View File

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

View File

@ -51,11 +51,21 @@
<span class="px-2 py-1 rounded-full text-xs font-semibold <span class="px-2 py-1 rounded-full text-xs font-semibold
@if($sewa->status == 'pending') bg-yellow-100 text-yellow-800 @if($sewa->status == 'pending') bg-yellow-100 text-yellow-800
@elseif($sewa->status == 'confirmed') bg-blue-100 text-blue-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 == 'dibatalkan') bg-red-100 text-red-800
@elseif($sewa->status == 'ditolak') bg-red-100 text-red-800
@endif"> @endif">
{{ ucfirst($sewa->status) }} {{ ucfirst($sewa->status) }}
</span> </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>
<td class="py-3 px-4"> <td class="py-3 px-4">
<div class="space-y-2"> <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 Detail
</button> </button>
@if(!in_array($sewa->status, ['completed', 'dibatalkan', 'ongoing'])) @if(!in_array($sewa->status, ['selesai', 'dibatalkan', 'ongoing']))
<form action="{{ route('riwayat.batal', $sewa->id) }}" method="POST"> <button type="button"
@csrf onclick="showCancelForm('{{ $sewa->id }}', '{{ $sewa->paket->nama_paket }}', '{{ $sewa->status }}')"
@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.' : ''))"
class="w-full bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600"> class="w-full bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
Batalkan Pesanan Batalkan Pesanan
</button> </button>
</form>
@endif @endif
@if(in_array($sewa->status, ['dibatalkan', 'completed'])) @if(in_array($sewa->status, ['dibatalkan', 'selesai']))
<form action="{{ route('riwayat.hapus', $sewa->id) }}" method="POST"> <form action="{{ route('riwayat.hapus', $sewa->id) }}" method="POST">
@csrf @csrf
@method('DELETE') @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>
</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 @else
<div class="text-center py-8"> <div class="text-center py-8">
<p class="text-gray-500">Belum ada riwayat pemesanan</p> <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"> <div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm">Tanggal Pembayaran: ${data.tanggal_pembayaran || '-'}</p> <p class="text-sm">Tanggal Pembayaran: ${data.tanggal_pembayaran || '-'}</p>
<p class="text-sm">Status: ${data.status_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>
</div> </div>
@ -233,6 +346,119 @@ function closeDetail() {
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> </script>
@endpush @endpush
@endsection @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> </div>
@endif @endif
@php @if($paket->stok_tersedia <= 0)
$activeSewa = $paket->sewas()
->whereIn('status', ['confirmed', 'ongoing'])
->count();
$tersedia = $paket->stok - $activeSewa;
@endphp
@if($tersedia <= 0)
<div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center"> <div class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="text-white text-center p-4"> <div class="text-white text-center p-4">
<i class="fas fa-clock text-3xl mb-2"></i> <i class="fas fa-clock text-3xl mb-2"></i>
<p class="font-bold">Sedang Disewa</p> <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>
</div> </div>
@endif @endif
@ -64,24 +57,20 @@ class="w-full h-full object-cover">
<span class="text-gray-600">Stok:</span> <span class="text-gray-600">Stok:</span>
<span class="font-semibold"> <span class="font-semibold">
{{ $paket->stok }} {{ $paket->stok }}
@if($activeSewa > 0) @if($paket->active_sewa_count > 0)
<span class="text-orange-500 text-sm ml-1">({{ $tersedia }} tersedia)</span> <span class="text-orange-500 text-sm ml-1">({{ $paket->stok_tersedia }} tersedia)</span>
@endif @endif
</span> </span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span class="text-gray-600">Status:</span> <span class="text-gray-600">Status:</span>
<span class="font-semibold <span class="font-semibold
@if($tersedia > 0) @if($paket->is_available)
text-green-600 text-green-600
@else @else
text-red-600 text-red-600
@endif"> @endif">
@if($tersedia > 0) {{ $paket->status_ketersediaan }}
Tersedia ({{ $tersedia }} unit)
@else
Sedang Disewa Semua
@endif
</span> </span>
</div> </div>
@ -114,7 +103,7 @@ class="w-full bg-gray-100 hover:bg-gray-200 text-gray-800 font-semibold py-2 px-
</button> </button>
@auth @auth
@if($tersedia <= 0) @if($paket->stok_tersedia <= 0)
<button disabled <button disabled
class="w-full bg-gray-400 text-white py-2 px-4 rounded cursor-not-allowed"> 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 <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 --> <!-- Kota Tujuan -->
<div> <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" <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" class="w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200"
required> required>
<option value="">Pilih Kota</option> <option value="">Pilih Kabupaten</option>
@foreach($paket->ongkirKota as $kota) @foreach($paket->ongkirKota as $kota)
<option value="{{ $kota->id }}" data-ongkir="{{ $kota->biaya_ongkir }}"> <option value="{{ $kota->id }}" data-ongkir="{{ $kota->biaya_ongkir }}">
{{ $kota->nama_kota }} - Rp {{ number_format($kota->biaya_ongkir, 0, ',', '.') }} {{ $kota->nama_kota }} - Rp {{ number_format($kota->biaya_ongkir, 0, ',', '.') }}

View File

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

View File

@ -62,13 +62,16 @@
bg-green-100 text-green-800 bg-green-100 text-green-800
@elseif($sewa->status === 'ongoing') @elseif($sewa->status === 'ongoing')
bg-blue-100 text-blue-800 bg-blue-100 text-blue-800
@elseif($sewa->status === 'completed') @elseif($sewa->status === 'selesai')
bg-purple-100 text-purple-800 bg-purple-100 text-purple-800
@else @else
bg-red-100 text-red-800 bg-red-100 text-red-800
@endif"> @endif">
{{ ucfirst($sewa->status) }} {{ ucfirst($sewa->status) }}
</span> </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>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
Rp {{ number_format($sewa->total_harga, 0, ',', '.') }} Rp {{ number_format($sewa->total_harga, 0, ',', '.') }}
@ -92,4 +95,26 @@ class="text-blue-600 hover:text-blue-900">
</div> </div>
</div> </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 @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"> <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 class="font-medium">Informasi Pembayaran:</p>
<p>tolong masukkan nominal pembayaran sebesar <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 segera lakukan pembayaran ke rekening dibawah ini
agar proses penyewaan ditangani lebih cepat agar proses penyewaan ditangani lebih cepat
jika ada masalah mohon hubungi admin di bagian hubungi kami jika ada masalah mohon hubungi admin di bagian hubungi kami
@ -130,18 +130,22 @@
<!-- Nominal Pembayaran --> <!-- Nominal Pembayaran -->
<div> <div>
<label for="nominal_pembayaran" class="block text-sm font-medium text-gray-700 mb-1"> <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> </label>
<div class="relative rounded-md shadow-sm"> <div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <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> <span class="text-gray-500 sm:text-sm">Rp</span>
</div> </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" 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> 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> </div>
@error('nominal_pembayaran') @error('nominal_pembayaran')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p> <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::post('/pengguna', [PenggunaController::class, 'store'])->name('pengguna.store');
Route::delete('/pengguna/{id}', [PenggunaController::class, 'destroy'])->name('pengguna.destroy'); 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 // Admin
Route::get('/admin', [AdminController::class, 'index'])->name('admin'); Route::get('/admin', [AdminController::class, 'index'])->name('admin');
Route::get('/admin/tambah', [AdminController::class, 'create'])->name('admin.create'); 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::delete('/riwayat/hapus/{id}', [\App\Http\Controllers\RiwayatController::class, 'hapus']);
Route::get('/riwayat', [\App\Http\Controllers\RiwayatController::class, 'index'])->name('riwayat'); 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 // Verifikasi Pembayaran
Route::get('/verifikasi', [App\Http\Controllers\Admin\VerifikasiController::class, 'index'])->name('verifikasi.index'); 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}/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::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 // Laporan Transaksi
Route::get('/reports/transactions', [App\Http\Controllers\Admin\ReportController::class, 'transactions'])->name('reports.transactions'); 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::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'); Route::put('/admin/riwayat/{id}/update-status', [App\Http\Controllers\Admin\ReportController::class, 'updateStatus'])->name('admin.riwayat.update-status');