change date to indonesian format, add total booking duration after add additional in formulir function, add detail validator in store function, edit template confirmation message

This commit is contained in:
LailaWulandarii 2026-01-07 17:15:27 +07:00
parent 9560793ff5
commit 98456552c7
1 changed files with 140 additions and 85 deletions

View File

@ -23,7 +23,7 @@ public function index()
public function detail($id) public function detail($id)
{ {
$foto = PaketFoto::findOrFail($id); $foto = PaketFoto::findOrFail($id);
\Carbon\Carbon::setLocale('id');
// Jika add-ons disimpan di DB, ambil juga: // Jika add-ons disimpan di DB, ambil juga:
$additionals = Additional::all(); $additionals = Additional::all();
// Logika Tanggal // Logika Tanggal
@ -33,23 +33,25 @@ public function detail($id)
$prevMonth = $start->copy()->subMonth(); $prevMonth = $start->copy()->subMonth();
$nextMonth = $start->copy()->addMonth(); $nextMonth = $start->copy()->addMonth();
$currentMonthLabel = $start->format('F Y'); // translatedFormat akan mengikuti locale 'id' di config Anda
$currentMonthLabel = $start->isoFormat('MMMM YYYY');
return view('user.detail-foto', compact('foto', 'additionals', 'start', 'end', 'currentMonthLabel', 'prevMonth', 'nextMonth')); return view('user.detail-foto', compact('foto', 'additionals', 'start', 'end', 'currentMonthLabel', 'prevMonth', 'nextMonth'));
} }
public function loadCalendar(Request $request) public function loadCalendar(Request $request)
{ {
\Carbon\Carbon::setLocale('id');
// Ambil bulan & tahun dari request AJAX, atau default sekarang // Ambil bulan & tahun dari request AJAX, atau default sekarang
// $month = $request->month ?? date('m');
// $year = $request->year ?? date('Y');
$month = $request->month ?? date('m'); $month = $request->month ?? date('m');
$year = $request->year ?? date('Y'); $year = $request->year ?? date('Y');
$start = \Carbon\Carbon::createFromDate($year, $month, 1); $start = \Carbon\Carbon::createFromDate($year, $month, 1);
// Data Navigasi // Data Navigasi
$prevMonth = $start->copy()->subMonth(); $prevMonth = $start->copy()->subMonth();
$nextMonth = $start->copy()->addMonth(); $nextMonth = $start->copy()->addMonth();
$currentMonthLabel = $start->format('F Y'); // translatedFormat akan mengikuti locale 'id' di config Anda
$currentMonthLabel = $start->isoFormat('MMMM YYYY'); // Return hanya potongan HTML (Partial), bukan halaman full
// Return hanya potongan HTML (Partial), bukan halaman full
$html = view('user.components.calendar-grid', compact( $html = view('user.components.calendar-grid', compact(
'start', 'start',
'prevMonth', 'prevMonth',
@ -74,31 +76,48 @@ public function formulir(Request $request)
{ {
// 1. Ambil Data Paket // 1. Ambil Data Paket
$foto = PaketFoto::findOrFail($request->id_paket); $foto = PaketFoto::findOrFail($request->id_paket);
$durasiDasar = $foto->durasi; // Ambil durasi paket asli
// 2. Hitung Total Add-ons (Jika ada) // 2. Hitung Total Add-ons & Tambahan Menit
$addonsDetails = []; $addonsDetails = [];
$totalAddon = 0; $totalAddon = 0;
$tambahanMenit = 0; // Inisialisasi tambahan waktu
if ($request->has('addons')) { if ($request->has('addons')) {
foreach ($request->addons as $id => $qty) { foreach ($request->addons as $id => $qty) {
if ($qty > 0) { if ($qty > 0) {
$add = Additional::find($id); $add = Additional::find($id);
$subtotal = $add->harga * $qty; if ($add) {
$totalAddon += $subtotal; $subtotal = $add->harga * $qty;
$totalAddon += $subtotal;
$addonsDetails[] = [ // LOGIKA TAMBAHAN WAKTU BERDASARKAN ID
'nama' => $add->nama, if ($id == 4) { // Tambah waktu/5 menit
'qty' => $qty, $tambahanMenit += (5 * $qty);
'subtotal' => $subtotal, } elseif ($id == 6) { // Tambah 10 menit sesi Spotlight
'id' => $id // Simpan ID untuk dikirim lagi nanti $tambahanMenit += (10 * $qty);
]; }
$addonsDetails[] = [
'nama' => $add->nama,
'qty' => $qty,
'subtotal' => $subtotal,
'id' => $id
];
}
} }
} }
} }
// 3. Hitung Grand Total // 3. Hitung Ulang Jam Selesai
$totalDurasi = $durasiDasar + $tambahanMenit;
$jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai);
$jamSelesaiBaru = $jamMulai->copy()->addMinutes($totalDurasi)->format('H:i');
// 4. Hitung Grand Total
$grandTotal = $foto->harga + $totalAddon; $grandTotal = $foto->harga + $totalAddon;
// 1. Cek apakah sudah ada deadline di session? Kalau belum, buat baru (2 jam dari sekarang)
// 5. Logika Deadline Pembayaran (Tetap sama)
if (!session()->has('payment_deadline')) { if (!session()->has('payment_deadline')) {
$deadline = now()->addHours(2); $deadline = now()->addHours(2);
session()->put('payment_deadline', $deadline); session()->put('payment_deadline', $deadline);
@ -106,21 +125,21 @@ public function formulir(Request $request)
$deadline = session('payment_deadline'); $deadline = session('payment_deadline');
} }
// 2. Hitung sisa waktu dalam detik $sisaWaktu = now()->diffInSeconds($deadline, false);
$sisaWaktu = now()->diffInSeconds($deadline, false); // false = biar bisa negatif kalau lewat
// 3. Jika waktu habis (negatif), hapus session dan tendang user
if ($sisaWaktu <= 0) { if ($sisaWaktu <= 0) {
session()->forget(['payment_deadline', 'addons']); // Bersihkan session session()->forget(['payment_deadline', 'addons']);
return redirect()->route('booking.foto')->with('error', 'Waktu pembayaran telah habis. Silakan ulang pemesanan.'); return redirect()->route('booking.foto')->with('error', 'Waktu pembayaran telah habis.');
} }
// 4. Kirim semua data ke View Pembayaran untuk ditampilkan
// 6. Kirim jam_selesai yang sudah diperbarui ke View
return view('user.pembayaran-foto', compact( return view('user.pembayaran-foto', compact(
'foto', 'foto',
'request', // Kirim request agar tgl & jam bisa diakses di blade 'request',
'addonsDetails', 'addonsDetails',
'grandTotal', 'grandTotal',
'sisaWaktu' 'sisaWaktu',
'jamSelesaiBaru' // Variabel baru untuk ditampilkan di blade
)); ));
} }
public function cancelBooking() public function cancelBooking()
@ -130,55 +149,47 @@ public function cancelBooking()
} }
public function store(Request $request) public function store(Request $request)
{ {
// 1. Validasi Input menggunakan Format Validator
// 1. Validasi Input $validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
$request->validate([
'id_paket' => 'required|exists:paket_fotos,id_paket', 'id_paket' => 'required|exists:paket_fotos,id_paket',
'tgl_booking' => 'required|date|after_or_equal:today', 'tgl_booking' => 'required|date|after_or_equal:today',
'jam_mulai' => 'required', 'jam_mulai' => 'required',
'nama' => 'required|string|max:255', 'nama' => 'required|string|min:3|max:100',
'no_wa' => 'required|numeric', 'no_wa' => 'required|numeric|digits_between:10,15',
'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048',
], [
// Detail Pesan Kustom
'required' => 'Kolom :attribute wajib diisi.',
'string' => 'Input :attribute harus berupa teks valid.',
'min' => ':attribute terlalu pendek, minimal :min karakter.',
'max' => ':attribute terlalu panjang, maksimal :max karakter.',
'numeric' => ':attribute harus berupa angka.',
'digits_between' => ':attribute harus antara :min sampai :max digit.',
'date' => 'Format tanggal pada :attribute tidak valid.',
'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.',
'image' => ':attribute harus berupa file gambar.',
'mimes' => 'Format :attribute harus jpeg, png, atau jpg.',
'max.file' => 'Ukuran :attribute maksimal adalah 2MB.',
], [
// Alias Atribut agar lebih ramah
'id_paket' => 'paket foto',
'nama' => 'nama pemesan',
'no_wa' => 'nomor WhatsApp',
'tgl_booking' => 'tanggal booking',
'jam_mulai' => 'jam mulai',
'bukti_bayar' => 'bukti pembayaran',
]); ]);
DB::beginTransaction(); // Mulai Transaksi Database if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
\Illuminate\Support\Facades\DB::beginTransaction(); // Mulai Transaksi Database
try { try {
// 2. Ambil Data Paket & Hitung Waktu // 2. Ambil Data Paket & Hitung Waktu (Termasuk Tambahan Menit dari Addons)
$paket = PaketFoto::findOrFail($request->id_paket); $paket = \App\Models\PaketFoto::findOrFail($request->id_paket);
$totalDurasi = $paket->durasi;
// 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; $grandTotal = $paket->harga;
$listAdditional = []; $listAdditional = [];
@ -190,32 +201,65 @@ public function store(Request $request)
$subtotal = $add->harga * $qty; $subtotal = $add->harga * $qty;
$grandTotal += $subtotal; $grandTotal += $subtotal;
// Logika Tambah Waktu berdasarkan ID yang sebelumnya kita bahas
if ($idAddon == 4) $totalDurasi += (5 * $qty); // Tambah 5 menit
if ($idAddon == 6) $totalDurasi += (10 * $qty); // Tambah 10 menit
$listAdditional[] = [ $listAdditional[] = [
'id_additional' => $idAddon, 'id_additional' => $idAddon,
'qty' => $qty, 'qty' => $qty,
'subtotal' => $subtotal 'subtotal' => $subtotal,
'nama_barang' => $add->nama
]; ];
} }
} }
} }
} }
// 7. Simpan Booking Utama $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai);
$booking = BookingFoto::create([ $jamSelesai = $jamMulai->copy()->addMinutes($totalDurasi);
'no_invoice' => 'INV-FOTO-' . strtoupper(Str::random(6)),
// 3. Cek Slot Sekali Lagi (Mencegah Race Condition)
$isTaken = \App\Models\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 = \App\Models\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() . $file->getClientOriginalExtension();
$file->move(public_path('img/payment'), $namaFile);
$pathBukti = 'img/payment/foto' . $namaFile;
}
// 6. Simpan Booking Utama
$booking = \App\Models\BookingFoto::create([
'no_invoice' => 'INV-FOTO-' . strtoupper(\Illuminate\Support\Str::random(6)),
'id_pelanggan' => $pelanggan->id_pelanggan, 'id_pelanggan' => $pelanggan->id_pelanggan,
'id_paket' => $paket->id_paket, 'id_paket' => $paket->id_paket,
'tgl_booking' => $request->tgl_booking, 'tgl_booking' => $request->tgl_booking,
'jam_mulai' => $request->jam_mulai, // Format "09:00" 'jam_mulai' => $request->jam_mulai,
'jam_selesai' => $jamSelesai->format('H:i'), 'jam_selesai' => $jamSelesai->format('H:i'),
'total_bayar' => $grandTotal, 'total_bayar' => $grandTotal,
'bukti_bayar' => $pathBukti, 'bukti_bayar' => $pathBukti,
'status_booking' => 'menunggu_verifikasi' 'status_booking' => 'menunggu_verifikasi'
]); ]);
// 8. Simpan Detail Additional (Jika ada) // 7. Simpan Detail Additional
foreach ($listAdditional as $item) { foreach ($listAdditional as $item) {
DetailAdditional::create([ \App\Models\DetailAdditional::create([
'id_booking' => $booking->id_booking, 'id_booking' => $booking->id_booking,
'id_additional' => $item['id_additional'], 'id_additional' => $item['id_additional'],
'qty' => $item['qty'], 'qty' => $item['qty'],
@ -223,13 +267,28 @@ public function store(Request $request)
]); ]);
} }
DB::commit(); \Illuminate\Support\Facades\DB::commit();
// 9. Redirect ke WhatsApp atau Halaman Sukses $txtAddons = "";
$pesan = "Halo Admin Flo.do! Saya pesan foto paket *{$paket->nama}*.\n" . if (count($listAdditional) > 0) {
"Tanggal: {$request->tgl_booking}\n" . $txtAddons = "*Additional:*"; // Judul
"Jam: {$request->jam_mulai}\n" . foreach ($listAdditional as $item) {
// Ambil nama yang tadi kita titip
$txtAddons .= "\n- " . $item['nama_barang'] . " (" . $item['qty'] . "x)";
}
$txtAddons .= "\n"; // Kasih jarak baris
}
// 8. Redirect ke WhatsApp
$pesan = "Halo Admin Flo.do! Saya sudah melakukan pembayaran untuk invoice {$booking->no_invoice}:" .
"*Data Pemesan:*\n" .
"Nama: {$pelanggan->nama}\n" .
"WA: {$pelanggan->no_wa}\n\n" .
"*Detail Booking:*\n" .
"Nama Paket: {$request->nama}\n" .
$txtAddons .
"Tanggal: " . \Carbon\Carbon::parse($request->tgl_booking)->translatedFormat('l, d F Y') . "\n" .
"Jam: {$request->jam_mulai} - {$jamSelesai->format('H:i')} WIB\n" .
"Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n" . "Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n" .
"Mohon diverifikasi ya."; "Mohon segera diproses, ya! Terima kasih.";
$urlWA = "https://wa.me/6289673668516?text=" . urlencode($pesan); $urlWA = "https://wa.me/6289673668516?text=" . urlencode($pesan);
session()->forget('payment_deadline'); session()->forget('payment_deadline');
@ -239,12 +298,8 @@ public function store(Request $request)
'waUrl' => $urlWA 'waUrl' => $urlWA
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); \Illuminate\Support\Facades\DB::rollBack();
// Teks Debugging Lengkap return back()->with('error', 'Error: ' . $e->getMessage())->withInput();
$errorMsg = "Error: " . $e->getMessage() . " | Baris: " . $e->getLine() . " | File: " . basename($e->getFile());
// Kirim pesan error lengkap ke layar
return back()->with('error', $errorMsg)->withInput();
} }
} }
} }