feat: Enhance Buket ordering process with AJAX search, validation, and new features

This commit is contained in:
LailaWulandarii 2025-12-28 19:22:26 +07:00
parent a5ae9a66d8
commit 24780837c2
7 changed files with 543 additions and 143 deletions

View File

@ -3,20 +3,156 @@
namespace App\Http\Controllers\User; namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Buket;
use App\Models\Pelanggan;
use App\Models\TransaksiBuket;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class PesanBuketController extends Controller class PesanBuketController extends Controller
{ {
public function index() public function index(Request $request)
{ {
return view('user/pesan-buket'); $query = Buket::query();
if ($request->filled('keyword')) {
$search = $request->keyword;
$query->where(function ($q) use ($search) {
$q->where('nama', 'like', '%' . $search . '%')
->orWhere('deskripsi', 'like', '%' . $search . '%');
});
} }
public function detail()
if ($request->filled('kategori') && $request->kategori != '') {
$query->where('kategori', $request->kategori);
}
if ($request->filled('min_price')) $query->where('harga', '>=', $request->min_price);
if ($request->filled('max_price')) $query->where('harga', '<=', $request->max_price);
if ($request->filled('ukuran')) $query->where('ukuran', $request->ukuran);
$buket = $query->paginate(8)->withQueryString();
if ($request->ajax()) {
return view('user._list', compact('buket'))->render();
}
return view('user/pesan-buket', compact('buket'));
}
public function detail($id)
{ {
return view('user/detail-buket'); $buket = Buket::findOrFail($id);
return view('user/detail-buket', compact('buket'));
} }
public function formulir() public function formulir($id)
{ {
return view('user/pembayaran-buket'); $buket = Buket::findOrFail($id);
return view('user/pembayaran-buket', compact('buket'));
}
public function store(Request $request)
{
$buket = Buket::findOrFail($request->id_buket);
// 1. Validasi Detail dengan Pesan Kustom & Alias
$validator = Validator::make($request->all(), [
'nama' => 'required|string|min:3|max:100',
'no_wa' => 'required|numeric|digits_between:10,15',
'tgl_ambil' => 'required|date|after_or_equal:today',
'waktu_ambil' => 'required',
'waktu_ambil' => [
'required',
'date_format:H:i',
'after_or_equal:09:00',
'before_or_equal:21:00',
],
'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048',
'request_khusus' => 'nullable|string|max:255',
'ucapan' => 'nullable|string|max:500',
], [
// Detail Pesan Kustom
'required' => 'Kolom :attribute wajib diisi.',
'string' => 'Input :attribute harus berupa teks valid.',
'min' => ':attribute terlalu pendek, minimal :min karakter.',
'max' => ':attribute terlalu panjang, maksimal :max karakter.',
'numeric' => ':attribute harus berupa angka.',
'digits_between' => ':attribute harus antara :min sampai :max digit.',
'date' => 'Format tanggal pada :attribute tidak valid.',
'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.',
'image' => ':attribute harus berupa file gambar.',
'mimes' => 'Format :attribute harus jpeg, png, atau jpg.',
'max.file' => 'Ukuran :attribute maksimal adalah 2MB.',
'waktu_ambil.after_or_equal' => 'Jam operasional kami mulai pukul 09:00.',
'waktu_ambil.before_or_equal' => 'Jam operasional kami berakhir pukul 21:00.',
], [
// Alias Atribut agar lebih ramah
'nama' => 'nama pemesan',
'no_wa' => 'nomor WhatsApp',
'tgl_ambil' => 'tanggal pengambilan',
'waktu_ambil' => 'waktu pengambilan',
'bukti_bayar' => 'bukti pembayaran',
'ucapan' => 'kartu ucapan',
]);
// Jika validasi gagal
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
try {
db::beginTransaction();
// 2. Simpan ke Tabel Pelanggan dulu
$pelanggan = Pelanggan::create([
'nama' => $request->nama,
'no_wa' => $request->no_wa,
]);
// 3. Handle File Upload Bukti Bayar
$namaFile = null;
if ($request->hasFile('bukti_bayar')) {
$file = $request->file('bukti_bayar');
// Membuat nama file unik berdasarkan waktu agar tidak tertimpa
$namaFile = time() . '_' . $file->getClientOriginalName();
// Pindahkan ke folder public/img/payment
$file->move(public_path('img/payment'), $namaFile);
}
$transaksi = TransaksiBuket::create([
'id_pelanggan' => $pelanggan->id_pelanggan,
'id_buket' => $request->id_buket,
'tgl_ambil' => $request->tgl_ambil . ' ' . $request->waktu_ambil,
'request' => $request->request_khusus, // Ubah dari request_khusus ke request
'ucapan' => $request->ucapan,
'bukti_bayar' => 'img/payment/' . $namaFile,
'status_transaksi' => 'menunggu_verifikasi', // Ubah dari status ke status_transaksi
'total_bayar' => $buket->harga, // Tambahkan ini karena total_bayar wajib di fillable
'no_invoice' => 'INV-' . time(), // Tambahkan invoice sederhana
]);
DB::commit();
// 5. Membuat Pesan WhatsApp Otomatis
$pesan = "Halo Admin Flo.do! Saya sudah melakukan pembayaran:\n\n" .
"*Data Pemesan:*\n" .
"Nama: {$pelanggan->nama}\n" .
"WA: {$pelanggan->no_wa}\n\n" .
"*Detail Produk:*\n" .
"Produk: {$transaksi->buket->nama}\n" .
"Total: Rp " . number_format($transaksi->buket->harga, 0, ',', '.') . "\n\n" .
"Mohon segera diproses, ya! Terima kasih.";
$urlWA = "https://wa.me/6289673668516?text=" . urlencode($pesan);
return redirect()->route('pesan.buket')->with([
'success' => 'Pesanan berhasil dikirim!',
'waUrl' => $urlWA
]);
} catch (\Exception $e) {
DB::rollBack();
return back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
} }
} }

View File

@ -17,6 +17,7 @@ class TransaksiBuket extends Model
'id_pelanggan', 'id_pelanggan',
'id_buket', 'id_buket',
'request', 'request',
'ucapan',
'tgl_ambil', 'tgl_ambil',
'total_bayar', 'total_bayar',
'bukti_bayar', 'bukti_bayar',
@ -33,4 +34,38 @@ public function buket()
{ {
return $this->belongsTo(Buket::class, 'id_buket'); return $this->belongsTo(Buket::class, 'id_buket');
} }
// Di Model TransaksiBuket.php
public function getStatusLabelAttribute()
{
// Mapping status ke class subtle dan teks
$statusMap = [
'menunggu_verifikasi' => [
'class' => 'bg-info-subtle', // text-emphasis agar kontras
'text' => 'Menunggu Verifikasi'
],
'diterima' => [
'class' => 'bg-warning-subtle ',
'text' => 'Diterima'
],
'selesai' => [
'class' => 'bg-success-subtle',
'text' => 'Selesai'
],
'dibatalkan' => [
'class' => 'bg-info-subtle ',
'text' => 'Dibatalkan'
],
'ditolak' => [
'class' => 'bg-danger-subtle',
'text' => 'Ditolak'
],
];
// Mengambil data berdasarkan status_transaksi, default ke secondary jika tidak ada
return (object) ($statusMap[$this->status_transaksi] ?? [
'class' => 'bg-secondary-subtle text-secondary',
'text' => 'Unknown'
]);
}
} }

View File

@ -14,26 +14,110 @@ public function run(): void
'nama' => 'Buket Mawar Merah Premium', 'nama' => 'Buket Mawar Merah Premium',
'harga' => 150000, 'harga' => 150000,
'deskripsi' => 'Buket mawar merah segar isi 10 tangkai dengan wrapping premium.', 'deskripsi' => 'Buket mawar merah segar isi 10 tangkai dengan wrapping premium.',
'request_khusus' => 'Tone Warna, Wrapping',
'kategori' => 'fresh', 'kategori' => 'fresh',
'ukuran' => 'S', 'ukuran' => 'S',
'foto' => 'img/buket/buket1.jpg', 'foto' => 'img/buket/mawar-merah-premium.jpg',
], ],
[ [
'nama' => 'Snack Bouquet Choco', 'nama' => 'Sunflower Bliss Bouquet',
'harga' => 75000, 'harga' => 125000,
'deskripsi' => 'Buket isi beng-beng, pocky, dan coklat silverqueen.', 'deskripsi' => 'Buket bunga matahari cerah yang melambangkan kebahagiaan.',
'kategori' => 'single', 'request_khusus' => 'Warna Pita, Kartu Ucapan',
'ukuran' => 'M', 'kategori' => 'fresh',
'foto' => 'img/buket/buket2.jpg', 'ukuran' => 'S',
'foto' => 'img/buket/sunflower.jpg',
], ],
[ [
'nama' => 'Money Bouquet 500k', 'nama' => 'Vintage Dried Rose',
'harga' => 550000, // Harga jasa + uang 'harga' => 200000,
'deskripsi' => 'Buket uang pecahan 50rb total 500rb, jasa rangkai free kartu ucapan.', 'deskripsi' => 'Kombinasi mawar kering dan edelweiss yang tahan lama bertahun-tahun.',
'request_khusus' => 'Warna Wrapping (Rustic/Pastel)',
'kategori' => 'artificial', 'kategori' => 'artificial',
'ukuran' => 'L', 'ukuran' => 'M',
'foto' => 'img/buket/buket3.jpg', 'foto' => 'img/buket/buket3.jpg',
], ],
[
'nama' => 'White Lily Elegance',
'harga' => 350000,
'deskripsi' => 'Buket Lily putih besar dengan aroma harum yang menenangkan.',
'request_khusus' => 'Jumlah Tangkai, Wrapping',
'kategori' => 'fresh',
'ukuran' => 'L',
'foto' => 'img/buket/white-lily.jpg',
],
[
'nama' => 'Artificial Tulip Garden',
'harga' => 110000,
'deskripsi' => 'Bunga Tulip latex kualitas premium yang terlihat sangat mirip asli.',
'request_khusus' => 'Tone Warna Tulip',
'kategori' => 'artificial',
'ukuran' => 'S',
'foto' => 'img/buket/artificial-tulip.jpg',
],
[
'nama' => 'Pastel Peony Dream',
'harga' => 275000,
'deskripsi' => 'Bunga Peony artificial dalam nuansa warna pastel yang lembut.',
'request_khusus' => 'Kombinasi Bunga Pendamping',
'kategori' => 'artificial',
'ukuran' => 'M',
'foto' => 'img/buket/pastel-peony.jpg',
],
[
'nama' => ' Lavender Scent',
'harga' => 185000,
'deskripsi' => 'Bunga lavender asli dengan aroma terapi alami, cocok untuk kado ultah.',
'request_khusus' => 'Wrapping Goni/Kertas',
'kategori' => 'fresh',
'ukuran' => 'S',
'foto' => 'img/buket/lavender.jpg',
],
[
'nama' => 'Grand Celebration Mix',
'harga' => 550000,
'deskripsi' => 'Buket raksasa campuran Mawar, Baby Breath, dan Hydrangea.',
'request_khusus' => 'Custom Jenis Bunga, Warna',
'kategori' => 'fresh',
'ukuran' => 'L',
'foto' => 'img/buket/gran-buket.jpg',
],
[
'nama' => 'Hydrangea Sky Blue',
'harga' => 165000,
'deskripsi' => 'Satu tangkai besar Hydrangea biru segar dengan filler Baby Breath.',
'request_khusus' => 'Tone Warna Hydrangea',
'kategori' => 'fresh',
'ukuran' => 'M',
'foto' => 'img/buket/hidraangea.jpg',
],
[
'nama' => 'Baby Breath Clouds',
'harga' => 140000,
'deskripsi' => 'Buket full Baby Breath putih yang tampak seperti awan, sangat simpel.',
'request_khusus' => 'Wrapping Celophane',
'kategori' => 'fresh',
'ukuran' => 'S',
'foto' => 'img/buket/babybreath.jpg',
],
[
'nama' => 'Single Sunflower',
'harga' => 45000,
'deskripsi' => 'Satu tangkai bunga matahari besar untuk mencerahkan hari seseorang.',
'request_khusus' => 'Warna Pita',
'kategori' => 'single',
'ukuran' => 'S',
'foto' => 'img/buket/single-sunflower.jpg',
],
[
'nama' => 'Pink Gerbera Cheer',
'harga' => 135000,
'deskripsi' => 'Buket bunga Gerbera pink ceria untuk menyemangati hari orang tersayang.',
'request_khusus' => 'Warna Pita',
'kategori' => 'fresh',
'ukuran' => 'S',
'foto' => 'img/buket/garbera.jpg',
],
]; ];
DB::table('bukets')->insert($data); DB::table('bukets')->insert($data);

View File

@ -0,0 +1,21 @@
<div id="product-container"> {{-- ID ini harus ada agar AJAX bisa menimpa isinya --}}
<div class="row g-3 d-flex align-items-stretch">
@forelse ($buket as $b)
<div class="col-6 col-md-4 col-lg-3">
<div class="katalogbuket-card h-100 position-relative">
<div class="img-wrapper mb-3">
<img src="{{ asset($b->foto) }}" class="img-fluid rounded-4" alt="{{ $b->nama }}">
</div>
<h6 class="katalogbuket-product-title">{{ $b->nama }}</h6>
<p class="katalogbuket-product-price">Rp {{ number_format($b->harga, 0, ',', '.') }}</p>
<div class="spacer"></div>
<a href="{{ route('detail.buket', $b->id_buket) }}" class="stretched-link"></a>
</div>
</div>
@empty
<div class="col-12 text-center py-5">
<p>Buket tidak ditemukan</p>
</div>
@endforelse
</div>
</div>

View File

@ -7,7 +7,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12 mb-2">
<a href="{{ route('pesan.buket') }}" class="text-decoration-none text-muted small"> <a href="{{ route('pesan.buket') }}" class="text-decoration-none text-muted small">
<i class="bi bi-arrow-left me-1"></i> Kembali ke Katalog <i class="bi bi-arrow-left me-1"></i> Kembali ke Katalog
</a> </a>
@ -18,16 +18,16 @@
<div class="col-lg-5 mb-5 mb-lg-0"> <div class="col-lg-5 mb-5 mb-lg-0">
<div class="detailbuket-img-frame"> <div class="detailbuket-img-frame">
<img src="{{ asset('img/hero-buket.jpg') }}" class="img-fluid" alt="Buket Lily Premium"> <img src="{{ asset($buket->foto) }}" class="img-fluid" alt="{{ $buket->nama }}">
</div> </div>
</div> </div>
<div class="col-lg-7"> <div class="col-lg-7">
<div class="detailbuket-content"> <div class="detailbuket-content">
<h1 class="detailbuket-title">Buket Lily Premium</h1> <h1 class="detailbuket-title">{{ $buket->nama }}</h1>
<h3 class="detailbuket-price">Rp 150.000</h3> <h3 class="detailbuket-price">Rp {{ number_format($buket->harga, 0, ',', '.') }}</h3>
<div class="detailbuket-specs d-flex align-items-center gap-4 mb-4"> <div class="detailbuket-specs d-flex align-items-center gap-4 mb-4">
<div class="detailbuket-specs d-flex align-items-center"> <div class="detailbuket-specs d-flex align-items-center">
@ -38,7 +38,8 @@
</div> </div>
<div class="d-flex flex-column gap-1"> <div class="d-flex flex-column gap-1">
<span class="detailbuket-spec-label">Kategori</span> <span class="detailbuket-spec-label">Kategori</span>
<span class="detailbuket-spec-val">Fresh Flowers</span> <span
class="detailbuket-spec-val">{{ ucfirst(str_replace('_', ' ', $buket->kategori)) }}</span>
</div> </div>
</div> </div>
@ -50,7 +51,7 @@
</div> </div>
<div class="d-flex flex-column gap-1"> <div class="d-flex flex-column gap-1">
<span class="detailbuket-spec-label">Ukuran</span> <span class="detailbuket-spec-label">Ukuran</span>
<span class="detailbuket-spec-val">Medium (40cm)</span> <span class="detailbuket-spec-val">Size {{ $buket->ukuran }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -58,27 +59,29 @@
</div> </div>
<div class="detailbuket-desc mt-4"> <div class="detailbuket-desc mt-4">
<p> <p>
Buket Lily Premium menghadirkan keanggunan dalam setiap helai kelopak lily segar berwarna {!! nl2br(e($buket->deskripsi)) !!}
putih atau pink, dipadukan dengan daun hijau dan sentuhan babys breath yang lembut.
</p>
<p>
Rangkaian ini dibalut kertas premium bernuansa nude atau ivory dengan pita satin senada,
menciptakan tampilan yang mewah dan menenangkan. Cocok untuk perayaan pernikahan, wisuda,
atau ucapan terima kasih.
</p> </p>
</div> </div>
<p class="detailbuket-note mt-4"> <p class="detailbuket-note mt-4">
<strong>Note:</strong> Penyesuaian warna bunga, wrapping, dan pita dapat ditulis di bagian <strong>Note:</strong> {{ $buket->request_khusus }}
catatan saat pemesanan.
</p> </p>
<div class="d-flex gap-3 mt-5"> <div class="d-flex gap-3 mt-5">
<a href="{{ route('formulir.buket') }}" class="btn btn-detailbuket-primary flex-fill"> <a href="{{ route('formulir.buket', ['id' => $buket->id_buket]) }}"
class="btn btn-detailbuket-primary flex-fill">
Beli Sekarang Beli Sekarang
</a> </a>
<a href="https://wa.me/6289673668516?text=Halo%20Flo.do,%20saya%20tertarik%20dengan%20produk%20*Buket%20Lily%20Premium*%20seharga%20Rp%20150.000.%20Apakah%20masih%20tersedia?" @php
target="_blank" class="btn btn-detailbuket-secondary flex-fill"> $waText =
'Halo Flo.do, saya tertarik dengan produk *' .
$buket->nama .
'* seharga Rp ' .
number_format($buket->harga, 0, ',', '.') .
'. Apakah masih tersedia?';
$waLink = 'https://wa.me/6289673668516?text=' . urlencode($waText);
@endphp
<a href="{{ $waLink }}" target="_blank" class="btn btn-detailbuket-secondary flex-fill">
Diskusikan di WA Diskusikan di WA
</a> </a>
</div> </div>

View File

@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<form action="#" method="POST" enctype="multipart/form-data"> <form action="{{ route('transaksi.buket.store') }}" method="POST" enctype="multipart/form-data">
@csrf @csrf
<div class="row g-4"> <div class="row g-4">
@ -22,21 +22,31 @@
<h4 class="formulirbuket-section-title mb-3">Rincian Pesanan</h4> <h4 class="formulirbuket-section-title mb-3">Rincian Pesanan</h4>
<div class="formulirbuket-card d-flex flex-column gap-2"> <div class="formulirbuket-card d-flex flex-column gap-2">
<div class="formulirbuket-product-summary d-flex align-items-center gap-3"> <div class="formulirbuket-product-summary d-flex align-items-center gap-3">
<img src="{{ asset('img/hero-buket.jpg') }}" alt="Product" class="rounded-3"> <img src="{{ asset($buket->foto) }}" alt="{{ $buket->nama }}" class="rounded-3">
<div> <div>
<h6 class=" mb-1">Buket Lily Premium</h6> <h6 class=" mb-1">{{ $buket->nama }}</h6>
<p class="text-teal mb-0">Rp 150.000</p> <p class="text-teal mb-0">Rp {{ number_format($buket->harga, 0, ',', '.') }}</p>
<input type="hidden" name="id_buket" value="{{ $buket->id_buket }}">
</div> </div>
</div> </div>
<div> <div>
<label class="form-label small">Request Khusus</label> <label class="form-label small">Request Khusus</label>
<input type="text" class="form-control formulirbuket-input" <input type="text" name="request_khusus"
placeholder="Masukkan Request Anda (Warna pita, dll)"> class="form-control formulirbuket-input @error('request_khusus') is-invalid @enderror"
placeholder="Masukkan Request Anda (Warna pita, dll)"
value="{{ old('request_khusus') }}">
@error('request_khusus')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div> </div>
<div> <div>
<label class="form-label small">Kartu Ucapan</label> <label class="form-label small">Kartu Ucapan</label>
<textarea class="form-control formulirbuket-input" rows="2" placeholder="Masukkan Teks untuk Kartu Ucapan"></textarea> <textarea class="form-control formulirbuket-input @error('ucapan') is-invalid @enderror" name="ucapan" rows="2"
placeholder="Masukkan Teks untuk Kartu Ucapan">{{ old('ucapan') }}</textarea>
@error('ucapan')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div> </div>
</div> </div>
</div> </div>
@ -45,24 +55,44 @@
<div class="formulirbuket-card d-flex flex-column gap-3"> <div class="formulirbuket-card d-flex flex-column gap-3">
<div> <div>
<label class="form-label small">Nama Lengkap</label> <label class="form-label small">Nama Lengkap</label>
<input type="text" class="form-control formulirbuket-input" <input type="text" name="nama"
placeholder="Masukkan Nama Lengkap"> class="form-control formulirbuket-input @error('nama') is-invalid @enderror"
placeholder="Masukkan Nama Lengkap" value="{{ old('nama') }}">
@error('nama')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</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 formulirbuket-input" <input type="number" name="no_wa"
placeholder="Masukkan Nomor WhatsApp"> class="form-control formulirbuket-input @error('no_wa') is-invalid @enderror"
placeholder="Masukkan Nomor WhatsApp" value="{{ old('no_wa') }}">
@error('no_wa')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div> </div>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small">Tanggal Pengambilan</label> <label class="form-label small">Tanggal Pengambilan</label>
<input type="date" class="form-control formulirbuket-input"> <input type="date" name="tgl_ambil"
class="form-control formulirbuket-input @error('tgl_ambil') is-invalid @enderror"
value="{{ old('tgl_ambil') }}">
@error('tgl_ambil')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small">Waktu Pengambilan</label> <label class="form-label small">Waktu Pengambilan</label>
<input type="time" class="form-control formulirbuket-input"> <input type="time" name="waktu_ambil"
class="form-control formulirbuket-input @error('waktu_ambil') is-invalid @enderror"
min="09:00" max="21:00" value="{{ old('waktu_ambil') }}">
<small class="text-muted">Jam operasional: 09.00 - 21.00 WIB</small>
@error('waktu_ambil')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div> </div>
</div> </div>
</div> </div>
@ -74,7 +104,7 @@
<div class="formulirbuket-payment-box"> <div class="formulirbuket-payment-box">
<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($buket->harga, 0, ',', '.') }}</h5>
</div> </div>
<p class="small mb-2">Transfer ke Rekening Berikut:</p> <p class="small mb-2">Transfer ke Rekening Berikut:</p>
@ -115,24 +145,27 @@ class="btn btn-sm btn-outline-secondary py-1 px-3 x-small btn-copy"
<p class="small mb-2">Upload Bukti Pembayaran</p> <p class="small mb-2">Upload Bukti Pembayaran</p>
<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" name="bukti_bayar" accept="image/*"
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 @error('bukti_bayar') is-invalid @enderror"
id="fileUpload"> id="fileUpload" value="{{ old('bukti_bayar') }}">
<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>
</div> </div>
</div> </div>
@error('bukti_bayar')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
<p class="x-small text-muted mb-4"> <p class="x-small text-muted mb-4">
*Note: Pastikan pembayaran sudah dilakukan sebelum mengunggah bukti, ya! *Note: Pastikan pembayaran sudah dilakukan sebelum mengunggah bukti, ya!
</p> </p>
<div class="d-flex gap-3"> <div class="d-flex gap-3">
<a href="{{ route('pesan.buket') }}" <a href="{{ route('detail.buket', $buket->id_buket) }}"
class="btn formulirbuket-btn-cancel flex-fill">Batalkan</a> class="btn formulirbuket-btn-cancel flex-fill">Batalkan</a>
<button type="submit" class="btn formulirbuket-btn-submit flex-fill">Kirim Pesanan</button> <button type="submit" class="btn formulirbuket-btn-submit flex-fill">Kirim
Pesanan</button>
</div> </div>
</div> </div>
@ -155,4 +188,22 @@ class="btn formulirbuket-btn-cancel flex-fill">Batalkan</a>
}); });
}); });
</script> </script>
<script>
// Preview Nama File setelah Upload
document.getElementById('fileUpload').addEventListener('change', function() {
const file = this.files[0];
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">${file.name}</p>
<p class="mb-0 x-small text-muted">Klik lagi untuk ganti file</p>
`;
}
});
</script>
@endsection @endsection

View File

@ -8,14 +8,15 @@
<div class="row justify-content-center mb-2"> <div class="row justify-content-center mb-2">
<div class="col-lg-8 text-center"> <div class="col-lg-8 text-center">
<h2 class="katalogbuket-title mb-2">Koleksi Bunga Flo.do</h2> <h2 class="katalogbuket-title mb-2">Koleksi Buket Flo.do</h2>
<p class="text-muted mb-4">Jelajahi berbagai pilihan buket dan rangkaian bunga dari Flodo</p> <p class="text-muted mb-4">Jelajahi berbagai pilihan buket dan rangkaian bunga dari Flodo</p>
<form action="{{ route('pesan.buket') }}" method="GET" class="w-100 d-flex justify-content-center"> <form action="{{ route('pesan.buket') }}" method="GET" class="w-100 d-flex justify-content-center">
<div class="katalogbuket-search-wrapper w-100"> <div class="katalogbuket-search-wrapper w-100">
<i class="bi bi-search search-icon"></i> <i class="bi bi-search search-icon"></i>
<input type="text" name="keyword" class="form-control katalogbuket-search-input" <input type="text" name="keyword" id="input-search"
placeholder="Cari bungamu di sini..." value="{{ request('keyword') }}"> class="form-control katalogbuket-search-input" placeholder="Cari bungamu di sini..."
value="{{ request('keyword') }}" autocomplete="off">
</div> </div>
</form> </form>
</div> </div>
@ -35,10 +36,14 @@
<div class="mb-4"> <div class="mb-4">
<ul class="list-unstyled katalogbuket-category-list"> <ul class="list-unstyled katalogbuket-category-list">
<li><a href="#">Single Flowers <i class="bi bi-chevron-right"></i></a></li> <li><a href="#" class="filter-cat" data-cat="single">Single Flowers <i
<li><a href="#">Fresh Flowers <i class="bi bi-chevron-right"></i></a></li> class="bi bi-chevron-right"></i></a></li>
<li><a href="#">Premium Fresh Flowers <i class="bi bi-chevron-right"></i></a></li> <li><a href="#" class="filter-cat" data-cat="fresh">Fresh Flowers <i
<li><a href="#">Artificial Flowers <i class="bi bi-chevron-right"></i></a></li> class="bi bi-chevron-right"></i></a></li>
<li><a href="#" class="filter-cat" data-cat="premium_fresh">Premium Fresh Flowers
<i class="bi bi-chevron-right"></i></a></li>
<li><a href="#" class="filter-cat" data-cat="artificial">Artificial Flowers <i
class="bi bi-chevron-right"></i></a></li>
</ul> </ul>
</div> </div>
@ -82,58 +87,56 @@
aria-labelledby="headingSize"> aria-labelledby="headingSize">
<div class="accordion-body px-0 pt-2"> <div class="accordion-body px-0 pt-2">
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button class="btn btn-size">S</button> <button class="btn btn-size filter-size" data-size="S">S</button>
<button class="btn btn-size">M</button> <button class="btn btn-size filter-size" data-size="M">M</button>
<button class="btn btn-size active">L</button> <button class="btn btn-size filter-size" data-size="L">L</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <button class="btn btn-katalogbuket-dark w-100 mt-4">Terapkan Filter</button> </div> <button id="btn-apply-filter" class="btn btn-katalogbuket-dark w-100 mt-4">Terapkan
Filter</button>
</div> </div>
</div> </div>
<div class="col-lg-9"> <div class="col-lg-9">
<div class="row g-3"> @include('user._list')
@for ($i = 1; $i <= 8; $i++)
<div class="col-6 col-md-4 col-lg-3">
<div class="katalogbuket-card position-relative">
<div class="img-wrapper mb-3">
<img src="{{ asset('img/hero-buket.jpg') }}" class="img-fluid rounded-4"
alt="Buket Bunga">
</div>
<h6 class="katalogbuket-product-title">Buket Lily Premium</h6>
<p class="katalogbuket-product-price">Rp 150.000</p>
<a href="{{ route('detail.buket') }}" class="stretched-link"></a>
</div>
</div>
@endfor
</div>
<div class="row mt-5"> <div class="row mt-5">
<div class="col-12 d-flex justify-content-between align-items-center"> <div class="col-12 d-flex justify-content-between align-items-center">
<a href="#" class="btn btn-pagination btn-sm rounded-pill px-3"> @if ($buket->onFirstPage())
<span class="btn btn-pagination btn-sm rounded-pill px-3 disabled">
<i class="bi bi-chevron-left me-1"></i> Sebelumnya
</span>
@else
<a href="{{ $buket->previousPageUrl() }}"
class="btn btn-pagination btn-sm rounded-pill px-3">
<i class="bi bi-chevron-left me-1"></i> Sebelumnya <i class="bi bi-chevron-left me-1"></i> Sebelumnya
</a> </a>
@endif
{{-- Nomor Halaman --}}
<div class="pagination-numbers text-muted"> <div class="pagination-numbers text-muted">
<span class="page-number active">1</span> @foreach ($buket->getUrlRange(1, $buket->lastPage()) as $page => $url)
<span class="page-number">2</span> @if ($page == $buket->currentPage())
<span class="page-number">3</span> <span class="page-number active">{{ $page }}</span>
<span class="page-number">...</span> @else
<span class="page-number">10</span> <a href="{{ $url }}"
class="page-number text-decoration-none">{{ $page }}</a>
@endif
@endforeach
</div> </div>
<a href="#" class="btn btn-pagination btn-sm rounded-pill px-3"> @if ($buket->hasMorePages())
<a href="{{ $buket->nextPageUrl() }}" class="btn btn-pagination btn-sm rounded-pill px-3">
Selanjutnya <i class="bi bi-chevron-right ms-1"></i> Selanjutnya <i class="bi bi-chevron-right ms-1"></i>
</a> </a>
@else
<span class="btn btn-pagination btn-sm rounded-pill px-3 disabled">
Selanjutnya <i class="bi bi-chevron-right ms-1"></i>
</span>
@endif
</div> </div>
</div> </div>
@ -142,53 +145,120 @@
</div> </div>
</div> </div>
</section> </section>
@endsection
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", function() { $(document).ready(function() {
var slider = document.getElementById('price-slider'); let timeout = null;
let selectedCat = '';
let selectedSize = '';
// Inisialisasi Slider // Fungsi Tunggal untuk Ambil Data (AJAX)
function fetchBuket(url = "{{ route('pesan.buket') }}") {
$.ajax({
url: url,
type: "GET",
data: {
keyword: $('#input-search').val(),
kategori: selectedCat,
ukuran: selectedSize,
min_price: $('#input-min').val(),
max_price: $('#input-max').val()
},
beforeSend: function() {
$('#product-container').css('opacity', '0.5');
},
success: function(data) {
$('#product-container').html(data);
$('#product-container').css('opacity', '1');
}
});
}
// Event Search (Live Search)
$('#input-search').on('keyup', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
fetchBuket();
}, 500);
});
// Event Pilih Kategori
$('.filter-cat').on('click', function(e) {
e.preventDefault();
$('.filter-cat').removeClass('active');
$(this).addClass('active');
selectedCat = $(this).data('cat');
// Jika ingin langsung filter tanpa klik tombol 'Terapkan', panggil fetchBuket() di sini
});
// Event Pilih Ukuran
$('.filter-size').on('click', function() {
// Reset semua tombol size ke style awal
$('.filter-size').removeClass('active');
// Tambahkan active ke yang diklik
$(this).addClass('active');
selectedSize = $(this).data('size');
});
// Klik Tombol Terapkan Filter
$('#btn-apply-filter').on('click', function() {
fetchBuket();
});
// Pagination AJAX
$(document).on('click', '.pagination a', function(e) {
e.preventDefault();
let url = $(this).attr('href');
fetchBuket(url); // Gunakan fungsi fetch dengan URL pagination
window.scrollTo(0, 400);
});
var slider = document.getElementById('price-slider');
noUiSlider.create(slider, { noUiSlider.create(slider, {
start: [50000, 200000], // Posisi awal handle start: [50000, 500000],
connect: true, // Ada warna di tengah handle connect: true,
range: { range: {
'min': 0, 'min': 0,
'max': 500000 'max': 1000000
}, },
step: 10000, // Kelipatan geser (10rb) step: 10000
format: {
to: function(value) {
return Math.round(value);
},
from: function(value) {
return Number(value);
}
}
}); });
// Update Teks Saat Digeser // Update label dan input hidden saat slider digeser
var minPriceText = document.getElementById('price-min');
var maxPriceText = document.getElementById('price-max');
var inputMin = document.getElementById('input-min');
var inputMax = document.getElementById('input-max');
slider.noUiSlider.on('update', function(values, handle) { slider.noUiSlider.on('update', function(values, handle) {
// Format Rupiah var min = Math.round(values[0]);
var formatter = new Intl.NumberFormat('id-ID', { var max = Math.round(values[1]);
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
});
if (handle === 0) { $('#price-min').text('Rp ' + min.toLocaleString('id-ID'));
minPriceText.innerHTML = formatter.format(values[0]); $('#price-max').text('Rp ' + max.toLocaleString('id-ID'));
inputMin.value = values[0]; $('#input-min').val(min);
} else { $('#input-max').val(max);
maxPriceText.innerHTML = formatter.format(values[1]);
inputMax.value = values[1];
}
}); });
}); });
</script> </script>
{{-- Letakkan di file yang rutenya pesan.buket --}}
@if (session('waUrl'))
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
// Tampilkan popup sukses dulu
Swal.fire({
title: 'Pesanan Berhasil!',
text: "{{ session('success') }}",
icon: 'success',
confirmButtonText: 'Konfirmasi WhatsApp',
confirmButtonColor: '#3B8181', // Warna Hijau WA
allowOutsideClick: false
}).then((result) => {
if (result.isConfirmed) {
// Buka WhatsApp di tab baru setelah klik tombol
window.open("{{ session('waUrl') }}", '_blank');
}
});
</script>
@endif
@endsection