get(); return view('user/booking-foto', compact('foto')); } public function detail($id) { $foto = PaketFoto::findOrFail($id); // Jika add-ons disimpan di DB, ambil juga: $additionals = Additional::all(); // Logika Tanggal $start = \Carbon\Carbon::now(); // Mulai hari ini $end = \Carbon\Carbon::now()->addMonth(); // Maksimal 1 bulan ke depan // Untuk navigasi panah $prevMonth = $start->copy()->subMonth(); $nextMonth = $start->copy()->addMonth(); $currentMonthLabel = $start->format('F Y'); return view('user.detail-foto', compact('foto', 'additionals', 'start', 'end', 'currentMonthLabel', 'prevMonth', 'nextMonth')); } public function loadCalendar(Request $request) { // Ambil bulan & tahun dari request AJAX, atau default sekarang $month = $request->month ?? date('m'); $year = $request->year ?? date('Y'); $start = \Carbon\Carbon::createFromDate($year, $month, 1); // Data Navigasi $prevMonth = $start->copy()->subMonth(); $nextMonth = $start->copy()->addMonth(); $currentMonthLabel = $start->format('F Y'); // Return hanya potongan HTML (Partial), bukan halaman full $html = view('user.components.calendar-grid', compact( 'start', 'prevMonth', 'nextMonth', 'currentMonthLabel' ))->render(); // Return JSON agar JavaScript bisa membacanya sebagai data.html return response()->json(['html' => $html]); } public function cekSlot(Request $request) { // Cari booking yang statusnya valid (bukan dibatalkan/ditolak) // Sesuaikan status dengan logic bisnis kamu $booked = BookingFoto::where('tgl_booking', $request->tanggal) ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima', 'selesai']) ->get(['jam_mulai']); return response()->json($booked); } public function formulir(Request $request) { // 1. Ambil Data Paket $foto = PaketFoto::findOrFail($request->id_paket); // 2. Hitung Total Add-ons (Jika ada) $addonsDetails = []; $totalAddon = 0; if ($request->has('addons')) { foreach ($request->addons as $id => $qty) { if ($qty > 0) { $add = Additional::find($id); $subtotal = $add->harga * $qty; $totalAddon += $subtotal; $addonsDetails[] = [ 'nama' => $add->nama, 'qty' => $qty, 'subtotal' => $subtotal, 'id' => $id // Simpan ID untuk dikirim lagi nanti ]; } } } // 3. Hitung Grand Total $grandTotal = $foto->harga + $totalAddon; // 1. Cek apakah sudah ada deadline di session? Kalau belum, buat baru (2 jam dari sekarang) if (!session()->has('payment_deadline')) { $deadline = now()->addHours(2); session()->put('payment_deadline', $deadline); } else { $deadline = session('payment_deadline'); } // 2. Hitung sisa waktu dalam detik $sisaWaktu = now()->diffInSeconds($deadline, false); // false = biar bisa negatif kalau lewat // 3. Jika waktu habis (negatif), hapus session dan tendang user if ($sisaWaktu <= 0) { session()->forget(['payment_deadline', 'addons']); // Bersihkan session return redirect()->route('booking.foto')->with('error', 'Waktu pembayaran telah habis. Silakan ulang pemesanan.'); } // 4. Kirim semua data ke View Pembayaran untuk ditampilkan return view('user.pembayaran-foto', compact( 'foto', 'request', // Kirim request agar tgl & jam bisa diakses di blade 'addonsDetails', 'grandTotal', 'sisaWaktu' )); } public function cancelBooking() { session()->forget(['payment_deadline', 'addons']); // Hapus session timer & data return redirect()->route('booking.foto'); // Kembali ke katalog utama } public function store(Request $request) { // 1. Validasi Input $request->validate([ 'id_paket' => 'required|exists:paket_fotos,id_paket', 'tgl_booking' => 'required|date|after_or_equal:today', 'jam_mulai' => 'required', 'nama' => 'required|string|max:255', 'no_wa' => 'required|numeric', 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', ]); DB::beginTransaction(); // Mulai Transaksi Database try { // 2. Ambil Data Paket & Hitung Waktu $paket = PaketFoto::findOrFail($request->id_paket); // Asumsi durasi default 20 menit (atau ambil dari database jika ada kolom durasi) $durasiMenit = $paket->durasi; $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); $jamSelesai = $jamMulai->copy()->addMinutes($durasiMenit); // 3. Cek Slot Sekali Lagi (Mencegah Race Condition) $isTaken = BookingFoto::where('tgl_booking', $request->tgl_booking) ->where('jam_mulai', $request->jam_mulai) ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima', 'selesai']) ->exists(); if ($isTaken) { return back()->with('error', 'Mohon maaf, slot waktu ini baru saja diambil orang lain.'); } // 4. Simpan/Update Data Pelanggan $pelanggan = Pelanggan::firstOrCreate( ['no_wa' => $request->no_wa], ['nama' => $request->nama] ); // 5. Upload Bukti Bayar $pathBukti = null; if ($request->hasFile('bukti_bayar')) { $file = $request->file('bukti_bayar'); $namaFile = 'bukti_' . time() . '_' . Str::random(5) . '.' . $file->getClientOriginalExtension(); $file->move(public_path('img/payment'), $namaFile); $pathBukti = 'img/payment/' . $namaFile; } // 6. Hitung Grand Total (Paket + Additional) // Kita hitung ulang di server agar aman dari manipulasi inspect element $grandTotal = $paket->harga; $listAdditional = []; if ($request->has('addons')) { foreach ($request->addons as $idAddon => $qty) { if ($qty > 0) { $add = \App\Models\Additional::find($idAddon); if ($add) { $subtotal = $add->harga * $qty; $grandTotal += $subtotal; $listAdditional[] = [ 'id_additional' => $idAddon, 'qty' => $qty, 'subtotal' => $subtotal ]; } } } } // 7. Simpan Booking Utama $booking = BookingFoto::create([ 'no_invoice' => 'INV-FOTO-' . strtoupper(Str::random(6)), 'id_pelanggan' => $pelanggan->id_pelanggan, 'id_paket' => $paket->id_paket, 'tgl_booking' => $request->tgl_booking, 'jam_mulai' => $request->jam_mulai, // Format "09:00" 'jam_selesai' => $jamSelesai->format('H:i'), 'total_bayar' => $grandTotal, 'bukti_bayar' => $pathBukti, 'status_booking' => 'menunggu_verifikasi' ]); // 8. Simpan Detail Additional (Jika ada) foreach ($listAdditional as $item) { DetailAdditional::create([ 'id_booking' => $booking->id_booking, 'id_additional' => $item['id_additional'], 'qty' => $item['qty'], 'subtotal' => $item['subtotal'] ]); } DB::commit(); // 9. Redirect ke WhatsApp atau Halaman Sukses $pesan = "Halo Admin Flo.do! Saya pesan foto paket *{$paket->nama}*.\n" . "Tanggal: {$request->tgl_booking}\n" . "Jam: {$request->jam_mulai}\n" . "Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n" . "Mohon diverifikasi ya."; $urlWA = "https://wa.me/6289673668516?text=" . urlencode($pesan); session()->forget('payment_deadline'); return redirect()->route('booking.foto')->with([ 'success' => 'Pesanan Berhasil Dibuat!', 'waUrl' => $urlWA ]); } catch (\Exception $e) { DB::rollBack(); // Teks Debugging Lengkap $errorMsg = "Error: " . $e->getMessage() . " | Baris: " . $e->getLine() . " | File: " . basename($e->getFile()); // Kirim pesan error lengkap ke layar return back()->with('error', $errorMsg)->withInput(); } } }