Refactor booking and payment flow for photo packages
- Updated booking-foto.blade.php to use forelse for displaying photo packages and added SweetAlert notifications for successful and error sessions. - Created a new calendar-grid component for better date selection in detail-foto.blade.php. - Enhanced detail-foto.blade.php to include a booking form with additional options and a calendar for selecting dates and times. - Improved pembayaran-foto.blade.php to handle errors, include hidden fields for previous selections, and add file upload preview functionality. - Updated web.php routes to better organize user and admin routes, including AJAX endpoints for calendar loading and slot checking.
This commit is contained in:
parent
24780837c2
commit
1f94e59459
|
|
@ -3,20 +3,248 @@
|
||||||
namespace App\Http\Controllers\User;
|
namespace App\Http\Controllers\User;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Additional;
|
||||||
|
use App\Models\BookingFoto;
|
||||||
|
use App\Models\DetailAdditional;
|
||||||
|
use App\Models\PaketFoto;
|
||||||
|
use App\Models\Pelanggan;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class BookingFotoController extends Controller
|
class BookingFotoController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('user/booking-foto');
|
$foto = PaketFoto::latest()->get();
|
||||||
|
|
||||||
|
return view('user/booking-foto', compact('foto'));
|
||||||
}
|
}
|
||||||
public function detail()
|
public function detail($id)
|
||||||
{
|
{
|
||||||
return view('user/detail-foto');
|
$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 formulir()
|
public function loadCalendar(Request $request)
|
||||||
{
|
{
|
||||||
return view('user/pembayaran-foto');
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public function up(): void
|
||||||
$table->string('bukti_bayar')->nullable();
|
$table->string('bukti_bayar')->nullable();
|
||||||
|
|
||||||
// Enum Status Final
|
// Enum Status Final
|
||||||
$table->enum('status_booking', ['menunggu_verifikasi', 'diproses', 'selesai', 'dibatalkan'])->default('menunggu_verifikasi');
|
$table->enum('status_booking', ['menunggu_verifikasi', 'diterima', 'ditolak', 'selesai', 'dibatalkan'])->default('menunggu_verifikasi');
|
||||||
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,60 @@ public function run(): void
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
[
|
[
|
||||||
'nama' => 'Wisuda Basic',
|
'nama' => 'Single',
|
||||||
'harga' => 250000,
|
'harga' => 20000,
|
||||||
'deskripsi' => 'Foto wisuda outdoor 1 jam, 10 edit file, all file mentah.',
|
'deskripsi' => 'Untuk 1 orang, 10 menit sesi foto sepuasnya, 5 menit sesi pilih foto (jika ada yang di print).',
|
||||||
'foto' => 'img/foto/foto1.jpeg',
|
'foto' => 'img/foto/single.jpeg',
|
||||||
'durasi' => '60',
|
'durasi' => '10',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'nama' => 'Couple Studio Session',
|
'nama' => 'Couple',
|
||||||
'harga' => 350000,
|
'harga' => 45000,
|
||||||
'deskripsi' => 'Foto studio couple 45 menit, 2 cetak 10R, 5 edit file.',
|
'deskripsi' => 'Untuk 2 orang, 15 menit sesi foto sepuasnya, 5 menit sesi pilih foto, 1 lembar print out foto ukutan 4R.',
|
||||||
'foto' => 'img/foto/foto2.jpeg',
|
'foto' => 'img/foto/grup.jpeg',
|
||||||
'durasi' => '45',
|
'durasi' => '15',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'nama' => 'Group Photoshoot (Max 10 Orang)',
|
'nama' => 'Group',
|
||||||
'harga' => 500000,
|
'harga' => 80000,
|
||||||
'deskripsi' => 'Foto grup, cocok untuk angkatan atau keluarga besar. Durasi 2 jam.',
|
'deskripsi' => 'Untuk 3-5 orang, 15 menit sesi foto sepuasnya, 5 menit sesi pilih foto, 3 lembar print out foto ukutan 4R.',
|
||||||
'foto' => 'img/foto/foto3.jpeg',
|
'foto' => 'img/foto/pas-foto.jpg',
|
||||||
'durasi' => '120',
|
'durasi' => '15',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama' => 'Pas Foto Paket 1',
|
||||||
|
'harga' => 25000,
|
||||||
|
'deskripsi' => 'Sesi pas foto untuk 1 orang dengan 8x shoot fotografer. Termasuk 1 file foto edit dan bebas request warna background. Paket cetak: ukuran 2x3 (12 lembar).',
|
||||||
|
'foto' => 'img/foto/pas-foto.jpg',
|
||||||
|
'durasi' => '0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama' => 'Pas Foto Paket 2',
|
||||||
|
'harga' => 25000,
|
||||||
|
'deskripsi' => 'Sesi pas foto untuk 1 orang dengan 8x shoot fotografer. Termasuk 1 file foto edit dan bebas request warna background. Paket cetak: ukuran 3x4 (8 lembar).',
|
||||||
|
'foto' => 'img/foto/pas-foto.jpg',
|
||||||
|
'durasi' => '0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama' => 'Pas Foto Paket 3',
|
||||||
|
'harga' => 25000,
|
||||||
|
'deskripsi' => 'Sesi pas foto untuk 1 orang dengan 8x shoot fotografer. Termasuk 1 file foto edit dan bebas request warna background. Paket cetak: ukuran 4x6 (4 lembar).',
|
||||||
|
'foto' => 'img/foto/pas-foto.jpg',
|
||||||
|
'durasi' => '0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama' => 'Pas Foto Paket 4',
|
||||||
|
'harga' => 25000,
|
||||||
|
'deskripsi' => 'Sesi pas foto untuk 1 orang dengan 8x shoot fotografer. Termasuk 1 file foto edit dan bebas request warna background. Paket cetak campur: ukuran 4x6 (2 lembar), 3x4 (3 lembar), dan 2x3 (4 lembar).',
|
||||||
|
'foto' => 'img/foto/pas-foto.jpg',
|
||||||
|
'durasi' => '0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'nama' => 'Background Biru',
|
||||||
|
'harga' => 95000,
|
||||||
|
'deskripsi' => 'Paket foto untuk 2 orang. Termasuk: Max 10x shoot fotografer (formal), 10 menit self-photo (bebas), dan semua file dikirim via Google Drive. Cetak 2 Foto (4R) terdiri dari: (2x3) 8 lembar, (3x4) 6 lembar, dan (4x6) 4 lembar.',
|
||||||
|
'foto' => 'img/foto/latar-biru.jpeg',
|
||||||
|
'durasi' => '20',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,27 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12" style="max-width: 850px; margin: 0 auto;">
|
<div class="col-12" style="max-width: 850px; margin: 0 auto;">
|
||||||
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 justify-content-center">
|
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 justify-content-center">
|
||||||
|
@forelse ($foto as $f)
|
||||||
@for ($i = 1; $i <= 8; $i++)
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="bookingfoto-card h-100">
|
<div class="bookingfoto-card position-relative">
|
||||||
<div class="img-wrapper mb-3">
|
<div class="img-wrapper mb-3">
|
||||||
<img src="{{ asset('img/hero-foto.jpg') }}" class="img-fluid rounded-4"
|
<img src="{{ asset($f->foto) }}" class="img-fluid rounded-4"
|
||||||
alt="Paket Foto">
|
alt="{{ $f->nama }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<h6 class="bookingfoto-product-title">Paket Self Photo {{ $i }}</h6>
|
<h6 class="bookingfoto-product-title">{{ $f->nama }}</h6>
|
||||||
<p class="bookingfoto-product-price">Rp 85.000</p>
|
<p class="bookingfoto-product-price">Rp {{ number_format($f->harga, 0, ',', '.') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('detail.foto') }}" class="stretched-link"></a>
|
<a href="{{ route('detail.foto', $f->id_paket) }}" class="stretched-link"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endfor
|
@empty
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<p>Paket foto tidak ditemukan</p>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -39,4 +44,39 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{{-- Cek session waUrl agar sama dengan pola buket --}}
|
||||||
|
@if (session('waUrl'))
|
||||||
|
{{-- Panggil library SweetAlert2 secara lokal di sini untuk memastikan ia ada --}}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Gunakan pola yang sama dengan buket yang sudah terbukti jalan
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Pesanan Berhasil!',
|
||||||
|
text: "{{ session('success') }}",
|
||||||
|
icon: 'success',
|
||||||
|
confirmButtonText: 'Konfirmasi WhatsApp',
|
||||||
|
confirmButtonColor: '#20c997',
|
||||||
|
allowOutsideClick: false
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// Buka WhatsApp di tab baru
|
||||||
|
window.open("{{ session('waUrl') }}", '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Pisahkan handler error --}}
|
||||||
|
@if (session('error'))
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<script>
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Gagal!',
|
||||||
|
text: "{{ session('error') }}",
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonColor: '#dc3545'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 calendar-nav">
|
||||||
|
<button type="button" class="btn btn-sm btn-light rounded-circle cal-nav-btn"
|
||||||
|
onclick="changeMonth({{ $prevMonth->month }}, {{ $prevMonth->year }})">
|
||||||
|
<i class="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="cal-month-label">{{ $currentMonthLabel }}</span>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-sm btn-light rounded-circle cal-nav-btn"
|
||||||
|
onclick="changeMonth({{ $nextMonth->month }}, {{ $nextMonth->year }})">
|
||||||
|
<i class="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detailfoto-date-grid">
|
||||||
|
<div class="day-name">Su</div>
|
||||||
|
<div class="day-name">Mo</div>
|
||||||
|
<div class="day-name">Tu</div>
|
||||||
|
<div class="day-name">We</div>
|
||||||
|
<div class="day-name">Th</div>
|
||||||
|
<div class="day-name">Fr</div>
|
||||||
|
<div class="day-name">Sa</div>
|
||||||
|
|
||||||
|
{{-- Offset Spasi --}}
|
||||||
|
@for ($i = 0; $i < $start->copy()->startOfMonth()->dayOfWeek; $i++)
|
||||||
|
<div class="date-item empty"></div>
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
{{-- Loop Tanggal --}}
|
||||||
|
@php $daysInMonth = $start->copy()->daysInMonth; @endphp
|
||||||
|
@for ($day = 1; $day <= $daysInMonth; $day++)
|
||||||
|
@php
|
||||||
|
$dateFull = $start->copy()->day($day);
|
||||||
|
// Logic Disabled: Lewat hari ini ATAU lebih dari 30 hari ke depan
|
||||||
|
$isPast = $dateFull->isPast() && !$dateFull->isToday();
|
||||||
|
$isTooFar = $dateFull->diffInDays(\Carbon\Carbon::now()) > 30;
|
||||||
|
$isDisabled = $isPast || $isTooFar;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="date-item {{ $isDisabled ? 'disabled' : '' }}" data-date="{{ $dateFull->format('Y-m-d') }}">
|
||||||
|
{{ $day }}
|
||||||
|
</div>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
|
@ -12,67 +12,55 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Seluruh Konten dalam Form agar data Add-ons & Slot terkirim --}}
|
||||||
|
<form id="form-booking" action="{{ route('formulir.foto') }}" method="GET">
|
||||||
|
@csrf
|
||||||
|
{{-- Kotak simpan data sementara di dalam form --}}
|
||||||
|
<input type="hidden" name="tgl_booking" id="input_tgl_booking">
|
||||||
|
<input type="hidden" name="jam_awal" id="input_jam_awal">
|
||||||
|
<input type="hidden" name="jam_selesai" id="input_jam_selesai">
|
||||||
|
<div id="data-paket" data-durasi="15"></div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-lg-6 mb-5" id="left-column">
|
<div class="col-lg-6 mb-5" id="left-column">
|
||||||
|
<input type="hidden" name="id_paket" value="{{ $foto->id_paket }}">
|
||||||
|
|
||||||
<div class="row gx-2">
|
<div class="row gx-3">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="detailfoto-card p-2 d-flex align-items-center h-100">
|
<div class="detailfoto-card p-2">
|
||||||
<img src="{{ asset('img/hero-foto.jpg') }}" alt="Paket Single"
|
<img src="{{ asset($foto->foto) }}" alt="{{ $foto->nama }}"
|
||||||
class="detailfoto-thumb rounded-4">
|
class="detailfoto-thumb rounded-4">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="detailfoto-card p-3 d-flex align-items-center h-100">
|
<div class="detailfoto-card p-3 h-100">
|
||||||
<div class="flex-grow-1">
|
<h3 class="detailfoto-product-title">{{ $foto->nama }}</h3>
|
||||||
<h3 class="detailfoto-product-title">Single</h3>
|
<h4 class="detailfoto-product-price">Rp {{ number_format($foto->harga, 0, ',', '.') }}
|
||||||
<h4 class="detailfoto-product-price">Rp 20.000</h4>
|
</h4>
|
||||||
<p class="detailfoto-product-desc mb-0"> Untuk 1 menit • 10 menit sesi foto sepuasnya •
|
<p class="detailfoto-product-desc mb-0 text-justify">{{ $foto->deskripsi }}</p>
|
||||||
5 menit sesi pilih foto (jika ada yang diprint)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="detailfoto-addons-title">Additional</h5>
|
<h5 class="detailfoto-addons-title mt-4">Additional</h5>
|
||||||
<div class="detailfoto-addons-wrapper">
|
<div class="detailfoto-addons-wrapper">
|
||||||
|
@foreach ($additionals as $add)
|
||||||
<div class="detailfoto-addon-item">
|
<div class="detailfoto-addon-item">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<span class="detailfoto-addon-name">Tambah orang untuk paket grup</span>
|
<span class="detailfoto-addon-name">{{ $add->nama }}</span>
|
||||||
<span class="detailfoto-addon-price">Rp 15.000</span>
|
<span class="detailfoto-addon-price">Rp
|
||||||
|
{{ number_format($add->harga, 0, ',', '.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detailfoto-counter">
|
<div class="detailfoto-counter">
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, -1)">-</button>
|
<button type="button" class="btn-counter"
|
||||||
<input type="text" value="0" readonly>
|
onclick="updateCounter(this, -1)">-</button>
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, 1)">+</button>
|
<input type="text" name="addons[{{ $add->id_additional }}]" value="0"
|
||||||
</div>
|
readonly>
|
||||||
</div>
|
<button type="button" class="btn-counter"
|
||||||
|
onclick="updateCounter(this, 1)">+</button>
|
||||||
<div class="detailfoto-addon-item">
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<span class="detailfoto-addon-name">Kostum boneka (onesize)/orang</span>
|
|
||||||
<span class="detailfoto-addon-price">Rp 10.000</span>
|
|
||||||
</div>
|
|
||||||
<div class="detailfoto-counter">
|
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, -1)">-</button>
|
|
||||||
<input type="text" value="0" readonly>
|
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, 1)">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detailfoto-addon-item">
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<span class="detailfoto-addon-name">Tambah print out foto 4R/lembar</span>
|
|
||||||
<span class="detailfoto-addon-price">Rp 7.000</span>
|
|
||||||
</div>
|
|
||||||
<div class="detailfoto-counter">
|
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, -1)">-</button>
|
|
||||||
<input type="text" value="0" readonly>
|
|
||||||
<button type="button" class="btn-counter" onclick="updateCounter(this, 1)">+</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-center" id="btn-booking-wrapper">
|
<div class="mt-4 text-center" id="btn-booking-wrapper">
|
||||||
|
|
@ -80,136 +68,282 @@ class="detailfoto-thumb rounded-4">
|
||||||
Booking Slot Foto
|
Booking Slot Foto
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6 d-none" id="calendar-column">
|
<div class="col-lg-6 d-none" id="calendar-column">
|
||||||
<div class="detailfoto-calendar-box">
|
<div class="detailfoto-calendar-box">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<span class="timer-alert-text">Slot Anda akan dibatalkan otomatis dalam</span>
|
<span class="timer-alert-text">Slot Anda akan dibatalkan otomatis dalam</span>
|
||||||
<span class="timer-badge">09:58</span>
|
<span class="timer-badge" id="booking-timer">10:00</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="schedule-title">Pilih Tanggal dan Waktu</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="schedule-title">Pilih Tanggal dan Waktu Pemotretan</h4>
|
|
||||||
|
|
||||||
<p class="schedule-desc">
|
|
||||||
Slot abu-abu berarti jadwal sudah penuh. Silakan pilih waktu lain yang tersedia.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
<div class="col-md-9 border-end pe-md-4" id="calendar-wrapper">
|
||||||
<div class="col-md-9 border-end pe-md-4">
|
@include('user.components.calendar-grid')
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3 calendar-nav">
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-light rounded-circle cal-nav-btn prev">
|
|
||||||
<i class="bi bi-chevron-left"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span class="cal-month-label">June 2025</span>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-light rounded-circle cal-nav-btn next">
|
|
||||||
<i class="bi bi-chevron-right"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detailfoto-date-grid">
|
<div class="col-md-3">
|
||||||
<div class="day-name">Su</div>
|
|
||||||
<div class="day-name">Mo</div>
|
|
||||||
<div class="day-name">Tu</div>
|
|
||||||
<div class="day-name">We</div>
|
|
||||||
<div class="day-name">Th</div>
|
|
||||||
<div class="day-name">Fr</div>
|
|
||||||
<div class="day-name">Sa</div>
|
|
||||||
|
|
||||||
<div class="date-item disabled">1</div>
|
|
||||||
<div class="date-item disabled">2</div>
|
|
||||||
<div class="date-item disabled">3</div>
|
|
||||||
<div class="date-item disabled">4</div>
|
|
||||||
<div class="date-item disabled">5</div>
|
|
||||||
<div class="date-item disabled">6</div>
|
|
||||||
<div class="date-item disabled">7</div>
|
|
||||||
<div class="date-item disabled">8</div>
|
|
||||||
<div class="date-item disabled">9</div>
|
|
||||||
<div class="date-item selected">10</div>
|
|
||||||
<div class="date-item">11</div>
|
|
||||||
<div class="date-item">12</div>
|
|
||||||
<div class="date-item">13</div>
|
|
||||||
<div class="date-item">14</div>
|
|
||||||
<div class="date-item">15</div>
|
|
||||||
<div class="date-item">16</div>
|
|
||||||
<div class="date-item">17</div>
|
|
||||||
<div class="date-item">18</div>
|
|
||||||
<div class="date-item">19</div>
|
|
||||||
<div class="date-item">20</div>
|
|
||||||
<div class="date-item">21</div>
|
|
||||||
<div class="date-item">22</div>
|
|
||||||
<div class="date-item">23</div>
|
|
||||||
<div class="date-item">24</div>
|
|
||||||
<div class="date-item">25</div>
|
|
||||||
<div class="date-item">26</div>
|
|
||||||
<div class="date-item">27</div>
|
|
||||||
<div class="date-item">28</div>
|
|
||||||
<div class="date-item">29</div>
|
|
||||||
<div class="date-item">30</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3 ps-md-3">
|
|
||||||
<h6 class="mb-3 small">Jam Tersedia</h6>
|
<h6 class="mb-3 small">Jam Tersedia</h6>
|
||||||
|
|
||||||
<div class="time-slot-container">
|
<div class="time-slot-container">
|
||||||
<button class="btn-time">09:00 AM</button>
|
@for ($hour = 9; $hour <= 20; $hour++)
|
||||||
<button class="btn-time">09:30 AM</button>
|
<button type="button" class="btn-time"
|
||||||
<button class="btn-time active">10:00 AM</button>
|
data-time="{{ sprintf('%02d:00', $hour) }}">
|
||||||
<button class="btn-time disabled">10:30 AM</button>
|
{{ sprintf('%02d:00', $hour) }} {{ $hour < 12 ? 'AM' : 'PM' }}
|
||||||
<button class="btn-time">11:00 AM</button>
|
</button>
|
||||||
<button class="btn-time">11:30 AM</button>
|
<button type="button" class="btn-time"
|
||||||
<button class="btn-time">12:00 PM</button>
|
data-time="{{ sprintf('%02d:30', $hour) }}">
|
||||||
<button class="btn-time">13:00 PM</button>
|
{{ sprintf('%02d:30', $hour) }} {{ $hour < 12 ? 'AM' : 'PM' }}
|
||||||
|
</button>
|
||||||
|
@endfor
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-3 mt-4 pt-4">
|
<div class="d-flex gap-3 mt-4 pt-4">
|
||||||
|
<button type="button" class="btn btn-action-cancel flex-fill"
|
||||||
<button class="btn btn-action-cancel flex-fill" onclick="hideCalendar()">
|
onclick="hideCalendar()">Batalkan</button>
|
||||||
Batalkan
|
<button type="submit" class="btn btn-action-submit flex-fill">Pesan Sekarang</button>
|
||||||
</button>
|
|
||||||
|
|
||||||
<a href="{{ route('formulir.foto') }}" class="btn btn-action-submit flex-fill">
|
|
||||||
Pesan Sekarang
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function showCalendar() {
|
// ============================================================
|
||||||
// 1. Sembunyikan tombol booking awal
|
// 1. VARIABLE GLOBAL & UTILITIES
|
||||||
document.getElementById('btn-booking-wrapper').style.display = 'none';
|
// ============================================================
|
||||||
|
let countdown; // Variabel untuk menyimpan timer
|
||||||
|
const durasiPaket = parseInt(document.getElementById('data-paket')?.dataset.durasi || 15);
|
||||||
|
|
||||||
// 2. Munculkan kolom kanan dengan animasi
|
// Fungsi Update Counter (+/-) untuk Additional
|
||||||
const calendarCol = document.getElementById('calendar-column');
|
|
||||||
calendarCol.classList.remove('d-none');
|
|
||||||
calendarCol.classList.add('fade-in-right'); // Custom CSS animation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fungsi Counter +/-
|
|
||||||
function updateCounter(btn, change) {
|
function updateCounter(btn, change) {
|
||||||
const input = btn.parentElement.querySelector('input');
|
const input = btn.parentElement.querySelector('input');
|
||||||
let newValue = parseInt(input.value) + change;
|
let currentValue = parseInt(input.value) || 0;
|
||||||
if (newValue < 0) newValue = 0;
|
let newValue = currentValue + change;
|
||||||
|
|
||||||
|
if (newValue < 0) newValue = 0; // Cegah minus
|
||||||
input.value = newValue;
|
input.value = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fungsi Tampilkan Kalender (Step 1 -> Step 2)
|
||||||
|
function showCalendar() {
|
||||||
|
document.getElementById('btn-booking-wrapper').style.display = 'none';
|
||||||
|
|
||||||
|
// DISABLE KOLOM KIRI
|
||||||
|
const leftColumn = document.getElementById('left-column');
|
||||||
|
if (leftColumn) leftColumn.classList.add('disabled-section');
|
||||||
|
|
||||||
|
const calendarCol = document.getElementById('calendar-column');
|
||||||
|
if (calendarCol) {
|
||||||
|
calendarCol.classList.remove('d-none');
|
||||||
|
calendarCol.classList.add('fade-in-right');
|
||||||
|
calendarCol.scrollIntoView({
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
|
||||||
|
startTimer(10 * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi Sembunyikan Kalender (Tombol Batalkan)
|
||||||
|
function hideCalendar() {
|
||||||
|
if (countdown) clearInterval(countdown);
|
||||||
|
|
||||||
|
document.getElementById('calendar-column').classList.add('d-none');
|
||||||
|
document.getElementById('btn-booking-wrapper').style.display = 'block';
|
||||||
|
|
||||||
|
// ENABLE KEMBALI KOLOM KIRI
|
||||||
|
const leftColumn = document.getElementById('left-column');
|
||||||
|
if (leftColumn) leftColumn.classList.remove('disabled-section');
|
||||||
|
|
||||||
|
// Scroll kembali ke atas (opsional, agar user sadar kolom kiri aktif lagi)
|
||||||
|
leftColumn.scrollIntoView({
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset pilihan tanggal & jam di input hidden
|
||||||
|
document.getElementById('input_tgl_booking').value = "";
|
||||||
|
document.getElementById('input_jam_awal').value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi Timer Mundur
|
||||||
|
function startTimer(duration) {
|
||||||
|
let timer = duration,
|
||||||
|
minutes, seconds;
|
||||||
|
const display = document.querySelector('#booking-timer');
|
||||||
|
|
||||||
|
// Reset timer lama jika ada
|
||||||
|
if (countdown) clearInterval(countdown);
|
||||||
|
|
||||||
|
countdown = setInterval(function() {
|
||||||
|
minutes = parseInt(timer / 60, 10);
|
||||||
|
seconds = parseInt(timer % 60, 10);
|
||||||
|
|
||||||
|
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||||
|
seconds = seconds < 10 ? "0" + seconds : seconds;
|
||||||
|
|
||||||
|
if (display) display.textContent = minutes + ":" + seconds;
|
||||||
|
|
||||||
|
if (--timer < 0) {
|
||||||
|
clearInterval(countdown);
|
||||||
|
Swal.fire('Waktu Habis!', 'Sesi booking Anda telah berakhir. Silakan pilih ulang.', 'warning')
|
||||||
|
.then(() => location.reload());
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Hitung Jam Selesai
|
||||||
|
function calculateEndTime(startTime, duration) {
|
||||||
|
let [hours, minutes] = startTime.split(':').map(Number);
|
||||||
|
minutes += duration;
|
||||||
|
|
||||||
|
if (minutes >= 60) {
|
||||||
|
hours += Math.floor(minutes / 60);
|
||||||
|
minutes = minutes % 60;
|
||||||
|
}
|
||||||
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. LOGIC AJAX & INTERAKSI KALENDER
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// A. Fungsi Ganti Bulan (Tanpa Refresh)
|
||||||
|
function changeMonth(month, year) {
|
||||||
|
const wrapper = document.getElementById('calendar-grid-wrapper');
|
||||||
|
|
||||||
|
// Efek loading
|
||||||
|
wrapper.style.opacity = '0.5';
|
||||||
|
|
||||||
|
// Panggil Controller via AJAX
|
||||||
|
fetch(`/load-calendar?month=${month}&year=${year}`)
|
||||||
|
.then(response => response.json()) // Pastikan controller return JSON { html: "..." }
|
||||||
|
.then(data => {
|
||||||
|
// Ganti isi wrapper dengan HTML baru dari server
|
||||||
|
wrapper.innerHTML = data.html;
|
||||||
|
wrapper.style.opacity = '1';
|
||||||
|
|
||||||
|
// PENTING: Pasang ulang listener karena elemen HTML-nya baru
|
||||||
|
reinitDateListeners();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Gagal load kalender:", err);
|
||||||
|
wrapper.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// B. Pasang Event Listener ke Tanggal (Re-usable)
|
||||||
|
function reinitDateListeners() {
|
||||||
|
const dateItems = document.querySelectorAll('.date-item:not(.disabled):not(.empty)');
|
||||||
|
|
||||||
|
dateItems.forEach(item => {
|
||||||
|
item.addEventListener('click', function() {
|
||||||
|
// UI: Reset warna selected lama
|
||||||
|
document.querySelectorAll('.date-item').forEach(el => el.classList.remove('selected'));
|
||||||
|
this.classList.add('selected');
|
||||||
|
|
||||||
|
// Logic: Simpan tanggal ke input hidden
|
||||||
|
const tgl = this.dataset.date;
|
||||||
|
const tglInput = document.getElementById('input_tgl_booking');
|
||||||
|
if (tglInput) tglInput.value = tgl;
|
||||||
|
|
||||||
|
// Logic: Cek Slot ke Database
|
||||||
|
checkSlotAvailability(tgl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// C. Cek Slot Penuh via AJAX
|
||||||
|
function checkSlotAvailability(tgl) {
|
||||||
|
// 1. Reset semua tombol jam jadi aktif dulu
|
||||||
|
const allTimeBtns = document.querySelectorAll('.btn-time');
|
||||||
|
allTimeBtns.forEach(btn => {
|
||||||
|
btn.classList.remove('disabled', 'full', 'active');
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.title = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Kosongkan input jam (karena user ganti tanggal)
|
||||||
|
document.getElementById('input_jam_awal').value = "";
|
||||||
|
|
||||||
|
// 3. Panggil Server
|
||||||
|
fetch(`/cek-slot-foto?tanggal=${tgl}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
data.forEach(booking => {
|
||||||
|
// Ambil jam depan saja (09:00:00 -> 09:00)
|
||||||
|
const jamPenuh = booking.jam_mulai.substring(0, 5);
|
||||||
|
|
||||||
|
// Cari tombol yg punya data-time sama
|
||||||
|
const btnPenuh = document.querySelector(`.btn-time[data-time="${jamPenuh}"]`);
|
||||||
|
|
||||||
|
if (btnPenuh) {
|
||||||
|
btnPenuh.classList.add('disabled', 'full'); // Tambah class styling
|
||||||
|
btnPenuh.disabled = true; // Matikan klik
|
||||||
|
btnPenuh.title = "Slot Sudah Terisi";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => console.error("Gagal cek slot:", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 3. INISIALISASI SAAT HALAMAN DIMUAT (DOM READY)
|
||||||
|
// ============================================================
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// 1. Jalankan Listener Tanggal (Untuk kalender awal)
|
||||||
|
reinitDateListeners();
|
||||||
|
|
||||||
|
// 2. Pasang Listener Tombol Jam (Cukup sekali)
|
||||||
|
const timeButtons = document.querySelectorAll('.btn-time');
|
||||||
|
timeButtons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Cek jika tombol disabled (safety)
|
||||||
|
if (this.disabled || this.classList.contains('disabled')) return;
|
||||||
|
|
||||||
|
// UI: Reset active
|
||||||
|
timeButtons.forEach(el => el.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
|
||||||
|
// Logic: Simpan Jam
|
||||||
|
const startTime = this.dataset.time;
|
||||||
|
document.getElementById('input_jam_awal').value = startTime;
|
||||||
|
|
||||||
|
// Logic: Hitung Jam Selesai
|
||||||
|
const endTime = calculateEndTime(startTime, durasiPaket);
|
||||||
|
document.getElementById('input_jam_selesai').value = endTime;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Validasi Submit Form
|
||||||
|
const bookingForm = document.getElementById('form-booking');
|
||||||
|
if (bookingForm) {
|
||||||
|
bookingForm.addEventListener('submit', function(e) {
|
||||||
|
const tgl = document.getElementById('input_tgl_booking').value;
|
||||||
|
const jam = document.getElementById('input_jam_awal').value;
|
||||||
|
|
||||||
|
if (!tgl || !jam) {
|
||||||
|
e.preventDefault(); // Stop kirim
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Jadwal Belum Dipilih!',
|
||||||
|
text: 'Silakan pilih tanggal dan jam pemotretan terlebih dahulu.',
|
||||||
|
icon: 'warning',
|
||||||
|
confirmButtonColor: '#20c997'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,28 @@
|
||||||
<h2 class="formulirfoto-page-title">Formulir Pemesanan</h2>
|
<h2 class="formulirfoto-page-title">Formulir Pemesanan</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if ($errors->any())
|
||||||
<form action="#" method="POST" enctype="multipart/form-data">
|
<div class="alert alert-danger">
|
||||||
|
<ul>
|
||||||
|
@foreach ($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<form action="{{ route('transaksi.foto.store') }}" method="POST" enctype="multipart/form-data">
|
||||||
@csrf
|
@csrf
|
||||||
|
{{-- DATA DARI HALAMAN SEBELUMNYA (WAJIB ADA) --}}
|
||||||
|
<input type="hidden" name="id_paket" value="{{ $foto->id_paket }}">
|
||||||
|
<input type="hidden" name="tgl_booking" value="{{ $request->tgl_booking }}">
|
||||||
|
<input type="hidden" name="jam_mulai" value="{{ $request->jam_mulai ?? $request->jam_awal }}">
|
||||||
|
|
||||||
|
{{-- Loop untuk mengirim ulang data Add-ons yang dipilih --}}
|
||||||
|
@if (!empty($addonsDetails))
|
||||||
|
@foreach ($addonsDetails as $add)
|
||||||
|
<input type="hidden" name="addons[{{ $add['id'] }}]" value="{{ $add['qty'] }}">
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|
||||||
<div class="col-lg-6 mb-5 mb-lg-0">
|
<div class="col-lg-6 mb-5 mb-lg-0">
|
||||||
|
|
@ -22,14 +41,14 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label small">Nama Lengkap</label>
|
<label class="form-label small">Nama Lengkap</label>
|
||||||
<input type="text" class="form-control formulirfoto-input"
|
<input type="text" name="nama" class="form-control formulirfoto-input"
|
||||||
placeholder="Masukkan Nama Lengkap">
|
placeholder="Masukkan Nama Lengkap" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label small">Nomor WhatsApp</label>
|
<label class="form-label small">Nomor WhatsApp</label>
|
||||||
<input type="number" class="form-control formulirfoto-input"
|
<input type="number" name="no_wa" class="form-control formulirfoto-input"
|
||||||
placeholder="Masukkan Nomor WhatsApp">
|
placeholder="Masukkan Nomor WhatsApp" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -45,30 +64,38 @@
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="summary-label">Jadwal Booking:</span>
|
<span class="summary-label">Jadwal Booking:</span>
|
||||||
<div class="summary-row">
|
<div class="summary-row">
|
||||||
<span class="summary-subtext">Thursday, June 10, 2025</span>
|
<span
|
||||||
<span class="summary-value">10:00 PM</span>
|
class="summary-subtext">{{ \Carbon\Carbon::parse($request->tgl_booking)->translatedFormat('l, d F Y') }}</span>
|
||||||
|
<span class="summary-value">{{ $request->jam_mulai }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="summary-label">Paket Foto:</span>
|
<span class="summary-label">Paket Foto:</span>
|
||||||
<div class="summary-row">
|
<div class="summary-row">
|
||||||
<span class="summary-subtext">Single</span>
|
<span class="summary-subtext">{{ $foto->nama }}</span>
|
||||||
<span class="summary-value">Rp 20.000</span>
|
<span class="summary-value">Rp {{ number_format($foto->harga, 0, ',', '.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-item">
|
<div class="summary-item">
|
||||||
<span class="summary-label">Additional:</span>
|
<span class="summary-label">Additional:</span>
|
||||||
|
@forelse($addonsDetails as $add)
|
||||||
<div class="summary-row">
|
<div class="summary-row">
|
||||||
<div class="row-left">
|
<div class="row-left">
|
||||||
<span class="summary-subtext">Kostum boneka (onesize)</span>
|
<span class="summary-subtext">{{ $add['nama'] }}</span>
|
||||||
<span class="summary-qty">x1</span>
|
<span class="summary-qty">x{{ $add['qty'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="summary-value">Rp 20.000</span>
|
<span class="summary-value">Rp
|
||||||
|
{{ number_format($add['subtotal'], 0, ',', '.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="summary-row">
|
||||||
|
<span class="summary-subtext text-muted">- Tidak ada tambahan -</span>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<span class="text-muted">Total Pembayaran</span>
|
<span class="text-muted">Total Pembayaran</span>
|
||||||
<h5 class=" mb-0">Rp 150.000</h5>
|
<h5 class=" mb-0">Rp {{ number_format($grandTotal, 0, ',', '.') }}</h5>
|
||||||
</div>
|
</div>
|
||||||
<p class="small mb-2">Transfer ke Rekening Berikut:</p>
|
<p class="small mb-2">Transfer ke Rekening Berikut:</p>
|
||||||
<div class="row g-2 mb-4">
|
<div class="row g-2 mb-4">
|
||||||
|
|
@ -110,8 +137,8 @@ class="btn btn-sm btn-outline-secondary py-1 px-3 x-small btn-copy"
|
||||||
<div class="formulirbuket-upload-area mb-2 text-center position-relative">
|
<div class="formulirbuket-upload-area mb-2 text-center position-relative">
|
||||||
<input type="file"
|
<input type="file"
|
||||||
class="position-absolute w-100 h-100 opacity-0 start-0 top-0 cursor-pointer"
|
class="position-absolute w-100 h-100 opacity-0 start-0 top-0 cursor-pointer"
|
||||||
id="fileUpload">
|
name="bukti_bayar" accept="image/*" id="fileUpload">
|
||||||
<div class="py-4">
|
<div class="py-4" id="uploadPlaceholder">
|
||||||
<i class="bi bi-file-earmark-arrow-up fs-3 text-secondary"></i>
|
<i class="bi bi-file-earmark-arrow-up fs-3 text-secondary"></i>
|
||||||
<p class="mb-0 small text-muted">Upload Bukti Pembayaran</p>
|
<p class="mb-0 small text-muted">Upload Bukti Pembayaran</p>
|
||||||
<p class="mb-0 x-small text-muted">Max. 2 MB</p>
|
<p class="mb-0 x-small text-muted">Max. 2 MB</p>
|
||||||
|
|
@ -123,9 +150,12 @@ class="position-absolute w-100 h-100 opacity-0 start-0 top-0 cursor-pointer"
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="{{ route('detail.foto') }}"
|
<a href="{{ route('booking.cancel') }}"
|
||||||
class="btn formulirfoto-btn-cancel flex-fill">Batalkan</a>
|
class="btn formulirfoto-btn-cancel flex-fill">Batalkan</a>
|
||||||
<button type="submit" class="btn formulirfoto-btn-submit flex-fill">Kirim Pesanan</button>
|
{{-- <a href="{{ route('detail.foto', $foto->id_paket) }}"
|
||||||
|
class="btn formulirfoto-btn-cancel flex-fill">Batalkan</a> --}}
|
||||||
|
<button type="submit" class="btn formulirfoto-btn-submit flex-fill">Kirim
|
||||||
|
Pesanan</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -137,7 +167,9 @@ class="btn formulirfoto-btn-cancel flex-fill">Batalkan</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Fitur Copy
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// 1. FITUR COPY NO REK
|
||||||
document.querySelectorAll('.btn-copy').forEach(btn => {
|
document.querySelectorAll('.btn-copy').forEach(btn => {
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
navigator.clipboard.writeText(this.getAttribute('data-clipboard-text'));
|
navigator.clipboard.writeText(this.getAttribute('data-clipboard-text'));
|
||||||
|
|
@ -149,29 +181,60 @@ class="btn formulirfoto-btn-cancel flex-fill">Batalkan</a>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fitur Timer Mundur (Simulasi)
|
// 2. FITUR PREVIEW UPLOAD
|
||||||
function startTimer(duration, display) {
|
const fileInput = document.getElementById('fileUpload');
|
||||||
var timer = duration,
|
if (fileInput) {
|
||||||
minutes, seconds;
|
fileInput.addEventListener('change', function() {
|
||||||
setInterval(function() {
|
const file = this.files[0];
|
||||||
minutes = parseInt(timer / 60, 10);
|
const placeholder = document.getElementById('uploadPlaceholder');
|
||||||
|
if (file) {
|
||||||
|
placeholder.innerHTML = `
|
||||||
|
<i class="bi bi-check-circle-fill fs-3 text-success"></i>
|
||||||
|
<p class="mb-0 small text-success fw-bold">${file.name}</p>
|
||||||
|
<p class="mb-0 x-small text-muted">Klik lagi untuk ganti file</p>
|
||||||
|
`;
|
||||||
|
// Optional: Kasih border hijau biar makin jelas
|
||||||
|
placeholder.parentElement.style.borderColor = "#198754";
|
||||||
|
placeholder.parentElement.style.backgroundColor = "#e8f5e9";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. FITUR TIMER MUNDUR
|
||||||
|
let sisaDetik = {{ $sisaWaktu }};
|
||||||
|
let display = document.querySelector('#countdown-timer');
|
||||||
|
|
||||||
|
function startTimer(duration) {
|
||||||
|
let timer = duration,
|
||||||
|
hours, minutes, seconds;
|
||||||
|
|
||||||
|
let interval = setInterval(function() {
|
||||||
|
hours = parseInt(timer / 3600, 10);
|
||||||
|
minutes = parseInt((timer % 3600) / 60, 10);
|
||||||
seconds = parseInt(timer % 60, 10);
|
seconds = parseInt(timer % 60, 10);
|
||||||
|
|
||||||
|
hours = hours < 10 ? "0" + hours : hours;
|
||||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||||
seconds = seconds < 10 ? "0" + seconds : seconds;
|
seconds = seconds < 10 ? "0" + seconds : seconds;
|
||||||
|
|
||||||
display.textContent = "00:" + minutes + ":" + seconds;
|
if (display) display.textContent = hours + ":" + minutes + ":" + seconds;
|
||||||
|
|
||||||
if (--timer < 0) {
|
if (--timer < 0) {
|
||||||
timer = duration;
|
clearInterval(interval);
|
||||||
|
alert("Waktu pembayaran habis!");
|
||||||
|
window.location.href =
|
||||||
|
"{{ route('booking.cancel') }}"; // Redirect ke cancel agar session bersih
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
// Jalankan timer
|
||||||
var fiftyEightMinutes = 60 * 58 + 58, // 58 menit 58 detik
|
if (sisaDetik > 0) {
|
||||||
display = document.querySelector('#countdown-timer');
|
startTimer(sisaDetik);
|
||||||
startTimer(fiftyEightMinutes, display);
|
} else {
|
||||||
};
|
// Jaga-jaga kalau sisaDetik 0 pas load (redirect langsung)
|
||||||
|
window.location.href = "{{ route('booking.cancel') }}";
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
110
routes/web.php
110
routes/web.php
|
|
@ -1,45 +1,95 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\Admin as Admin; // Import namespace Admin
|
use App\Http\Controllers\Admin as Admin;
|
||||||
|
use App\Http\Controllers\User as User;
|
||||||
|
use App\Http\Controllers\AuthController; // Asumsi controller login dipisah atau di Admin
|
||||||
|
use App\Http\Controllers\User\TestBookingController;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 1. PUBLIK / GUEST (Pelanggan)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Bebas akses, tidak perlu login.
|
||||||
|
*/
|
||||||
|
|
||||||
Route::group([], function () {
|
Route::group([], function () {
|
||||||
Route::get('/', [App\Http\Controllers\User\BerandaController::class, 'index'])->name('beranda');
|
Route::get('/', [User\BerandaController::class, 'index'])->name('beranda');
|
||||||
Route::get('/login', [App\Http\Controllers\Admin\AuthController::class, 'login'])->name('login');
|
|
||||||
Route::get('/pesan-buket', [App\Http\Controllers\User\PesanBuketController::class, 'index'])->name('pesan.buket');
|
// Fitur Buket
|
||||||
Route::get('/detail-buket', [App\Http\Controllers\User\PesanBuketController::class, 'detail'])->name('detail.buket');
|
Route::get('/pesan-buket', [User\PesanBuketController::class, 'index'])->name('pesan.buket');
|
||||||
Route::get('/formulir-pemesanan-buket', [App\Http\Controllers\User\PesanBuketController::class, 'formulir'])->name('formulir.buket');
|
Route::get('/pesan-buket/{id}', [User\PesanBuketController::class, 'detail'])->name('detail.buket');
|
||||||
Route::get('/booking-foto', [App\Http\Controllers\User\BookingFotoController::class, 'index'])->name('booking.foto');
|
Route::get('/formulir-buket/{id}', [User\PesanBuketController::class, 'formulir'])->name('formulir.buket');
|
||||||
Route::get('/detail-paket-foto', [App\Http\Controllers\User\BookingFotoController::class, 'detail'])->name('detail.foto');
|
Route::post('/formulir-buket/store', [User\PesanBuketController::class, 'store'])->name('transaksi.buket.store');
|
||||||
Route::get('/formulir-pemesanan-foto', [App\Http\Controllers\User\BookingFotoController::class, 'formulir'])->name('formulir.foto');
|
// Fitur Foto
|
||||||
|
Route::get('/booking-foto', [User\BookingFotoController::class, 'index'])->name('booking.foto');
|
||||||
|
Route::get('/detail-paket-foto/{id}', [User\BookingFotoController::class, 'detail'])->name('detail.foto'); // Tambah {id}
|
||||||
|
Route::get('/formulir-pemesanan-foto', [User\BookingFotoController::class, 'formulir'])->name('formulir.foto');
|
||||||
|
|
||||||
|
Route::get('/load-calendar', [User\BookingFotoController::class, 'loadCalendar'])->name('ajax.load-calendar');
|
||||||
|
Route::get('/cek-slot-foto', [User\BookingFotoController::class, 'cekSlot'])->name('ajax.check-slot');
|
||||||
|
|
||||||
|
Route::post('/simpan-booking-foto', [User\BookingFotoController::class, 'store'])->name('transaksi.foto.store');
|
||||||
|
Route::get('/cancel-booking', [User\BookingFotoController::class, 'cancelBooking'])->name('booking.cancel');
|
||||||
|
|
||||||
|
Route::get('/test-flow', [User\TestBookingController::class, 'index']);
|
||||||
|
Route::get('/cek-slot-foto', [User\TestBookingController::class, 'cekSlot']);
|
||||||
|
Route::post('/test-flow-simpan', [User\TestBookingController::class, 'store'])->name('test.simpan.foto');
|
||||||
});
|
});
|
||||||
Route::prefix('admin')->name('admin.')->group(function () {
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 2. AUTENTIKASI (Login/Logout)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
// Menampilkan form login
|
||||||
|
Route::get('/login', [Admin\AuthController::class, 'login'])->name('login')->middleware('guest');
|
||||||
|
// Memproses data login (YANG SEBELUMNYA KURANG)
|
||||||
|
Route::post('/login', [Admin\AuthController::class, 'authenticate'])->name('login.proses');
|
||||||
|
// Logout
|
||||||
|
Route::post('/logout', [Admin\AuthController::class, 'logout'])->name('logout');
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 3. ADMIN & OWNER PANEL
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
Route::prefix('admin')->name('admin.')->middleware(['auth'])->group(function () {
|
||||||
|
|
||||||
Route::redirect('/', '/admin/beranda');
|
Route::redirect('/', '/admin/beranda');
|
||||||
Route::get('/beranda', [Admin\BerandaController::class, 'admin'])->name('beranda');
|
|
||||||
Route::get('/beranda-pemilik', [Admin\BerandaController::class, 'pemilik'])->name('beranda.pemilik');
|
|
||||||
|
|
||||||
Route::resource('pesanan-buket', Admin\PesananBuketController::class);
|
// Dashboard
|
||||||
Route::resource('pesanan-foto', Admin\PesananFotoController::class);
|
Route::get('/beranda', [Admin\BerandaController::class, 'index'])->name('beranda');
|
||||||
|
|
||||||
|
Route::get('/pesanan-buket', [Admin\PesananBuketController::class, 'index'])
|
||||||
|
->name('pesanan-buket.index');
|
||||||
|
|
||||||
|
// Rute Update Status (Put) - Pastikan nama ini unik
|
||||||
|
Route::put('/pesanan-buket/update-status/{id}', [Admin\PesananBuketController::class, 'updateStatus'])
|
||||||
|
->name('pesanan-buket.update-status');
|
||||||
|
|
||||||
|
Route::get('/pesanan-foto', [Admin\PesananFotoController::class, 'index'])
|
||||||
|
->name('pesanan-foto.index');
|
||||||
|
|
||||||
|
// Rute Update Status (Put) - Pastikan nama ini unik
|
||||||
|
Route::put('/pesanan-foto/update-status/{id}', [Admin\PesananFotoController::class, 'updateStatus'])
|
||||||
|
->name('pesanan-foto.update-status');
|
||||||
|
|
||||||
Route::get('/riwayat-pesanan', [Admin\HistoriPesananController::class, 'index'])->name('riwayat');
|
Route::get('/riwayat-pesanan', [Admin\HistoriPesananController::class, 'index'])->name('riwayat');
|
||||||
|
|
||||||
Route::resource('produk-buket', App\Http\Controllers\Admin\BuketController::class);
|
// Manajemen Produk (Master Data)
|
||||||
Route::resource('paket-foto', App\Http\Controllers\Admin\FotoController::class);
|
Route::resource('produk-buket', Admin\BuketController::class); // Perbaiki namespace jika perlu
|
||||||
|
Route::resource('paket-foto', Admin\FotoController::class); // Perbaiki namespace jika perlu
|
||||||
// --- 2. OWNER ONLY ROUTES (Khusus Pemilik) ---
|
Route::resource('additional', Admin\AdditionalController::class)->except(['index', 'show']);
|
||||||
// Kita bungkus dengan logic middleware sederhana atau Gate
|
|
||||||
// Route::group(['middleware' => function ($request, $next) {
|
|
||||||
// // Cek: Kalau bukan pemilik, lempar error 403 (Forbidden)
|
|
||||||
// if (auth()->user()->role !== 'pemilik') {
|
|
||||||
// abort(403, 'Akses Ditolak. Halaman ini khusus Pemilik.');
|
|
||||||
// }
|
|
||||||
// return $next($request);
|
|
||||||
// }], function () {
|
|
||||||
|
|
||||||
// // Menu ini hanya bisa dibuka Pemilik
|
|
||||||
// Route::resource('kelola-admin', Admin\ManajemenAdminController::class);
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
// Manajemen User (Khusus Owner)
|
||||||
Route::resource('kelola-admin', Admin\ManajemenAdminController::class);
|
Route::resource('kelola-admin', Admin\ManajemenAdminController::class);
|
||||||
|
|
||||||
|
// Profil Diri
|
||||||
Route::resource('profil', Admin\ProfilController::class);
|
Route::resource('profil', Admin\ProfilController::class);
|
||||||
|
Route::put('/profil/update', [Admin\ProfilController::class, 'update'])->name('profil.simpan');
|
||||||
|
|
||||||
|
Route::put('/profil/password', [Admin\ProfilController::class, 'updatePassword'])->name('profil.password');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue