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:
LailaWulandarii 2025-12-28 19:23:42 +07:00
parent 24780837c2
commit 1f94e59459
8 changed files with 885 additions and 291 deletions

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

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