feat: Enhance Buket ordering process with AJAX search, validation, and new features
This commit is contained in:
parent
a5ae9a66d8
commit
24780837c2
|
|
@ -3,20 +3,156 @@
|
|||
namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Buket;
|
||||
use App\Models\Pelanggan;
|
||||
use App\Models\TransaksiBuket;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class TransaksiBuket extends Model
|
|||
'id_pelanggan',
|
||||
'id_buket',
|
||||
'request',
|
||||
'ucapan',
|
||||
'tgl_ambil',
|
||||
'total_bayar',
|
||||
'bukti_bayar',
|
||||
|
|
@ -33,4 +34,38 @@ public function 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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,26 +14,110 @@ public function run(): void
|
|||
'nama' => 'Buket Mawar Merah Premium',
|
||||
'harga' => 150000,
|
||||
'deskripsi' => 'Buket mawar merah segar isi 10 tangkai dengan wrapping premium.',
|
||||
'request_khusus' => 'Tone Warna, Wrapping',
|
||||
'kategori' => 'fresh',
|
||||
'ukuran' => 'S',
|
||||
'foto' => 'img/buket/buket1.jpg',
|
||||
'foto' => 'img/buket/mawar-merah-premium.jpg',
|
||||
],
|
||||
[
|
||||
'nama' => 'Snack Bouquet Choco',
|
||||
'harga' => 75000,
|
||||
'deskripsi' => 'Buket isi beng-beng, pocky, dan coklat silverqueen.',
|
||||
'kategori' => 'single',
|
||||
'ukuran' => 'M',
|
||||
'foto' => 'img/buket/buket2.jpg',
|
||||
'nama' => 'Sunflower Bliss Bouquet',
|
||||
'harga' => 125000,
|
||||
'deskripsi' => 'Buket bunga matahari cerah yang melambangkan kebahagiaan.',
|
||||
'request_khusus' => 'Warna Pita, Kartu Ucapan',
|
||||
'kategori' => 'fresh',
|
||||
'ukuran' => 'S',
|
||||
'foto' => 'img/buket/sunflower.jpg',
|
||||
],
|
||||
[
|
||||
'nama' => 'Money Bouquet 500k',
|
||||
'harga' => 550000, // Harga jasa + uang
|
||||
'deskripsi' => 'Buket uang pecahan 50rb total 500rb, jasa rangkai free kartu ucapan.',
|
||||
'nama' => 'Vintage Dried Rose',
|
||||
'harga' => 200000,
|
||||
'deskripsi' => 'Kombinasi mawar kering dan edelweiss yang tahan lama bertahun-tahun.',
|
||||
'request_khusus' => 'Warna Wrapping (Rustic/Pastel)',
|
||||
'kategori' => 'artificial',
|
||||
'ukuran' => 'L',
|
||||
'ukuran' => 'M',
|
||||
'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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<div class="container">
|
||||
|
||||
<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">
|
||||
<i class="bi bi-arrow-left me-1"></i> Kembali ke Katalog
|
||||
</a>
|
||||
|
|
@ -18,16 +18,16 @@
|
|||
|
||||
<div class="col-lg-5 mb-5 mb-lg-0">
|
||||
<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 class="col-lg-7">
|
||||
<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">
|
||||
|
|
@ -38,7 +38,8 @@
|
|||
</div>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<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>
|
||||
|
||||
|
|
@ -50,7 +51,7 @@
|
|||
</div>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<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>
|
||||
|
|
@ -58,27 +59,29 @@
|
|||
</div>
|
||||
<div class="detailbuket-desc mt-4">
|
||||
<p>
|
||||
Buket Lily Premium menghadirkan keanggunan dalam setiap helai kelopak lily segar berwarna
|
||||
putih atau pink, dipadukan dengan daun hijau dan sentuhan baby’s 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.
|
||||
{!! nl2br(e($buket->deskripsi)) !!}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="detailbuket-note mt-4">
|
||||
<strong>Note:</strong> Penyesuaian warna bunga, wrapping, dan pita dapat ditulis di bagian
|
||||
catatan saat pemesanan.
|
||||
<strong>Note:</strong> {{ $buket->request_khusus }}
|
||||
</p>
|
||||
|
||||
<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
|
||||
</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?"
|
||||
target="_blank" class="btn btn-detailbuket-secondary flex-fill">
|
||||
@php
|
||||
$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
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form action="#" method="POST" enctype="multipart/form-data">
|
||||
<form action="{{ route('transaksi.buket.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="row g-4">
|
||||
|
||||
|
|
@ -22,21 +22,31 @@
|
|||
<h4 class="formulirbuket-section-title mb-3">Rincian Pesanan</h4>
|
||||
<div class="formulirbuket-card d-flex flex-column gap-2">
|
||||
<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>
|
||||
<h6 class=" mb-1">Buket Lily Premium</h6>
|
||||
<p class="text-teal mb-0">Rp 150.000</p>
|
||||
<h6 class=" mb-1">{{ $buket->nama }}</h6>
|
||||
<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>
|
||||
<label class="form-label small">Request Khusus</label>
|
||||
<input type="text" class="form-control formulirbuket-input"
|
||||
placeholder="Masukkan Request Anda (Warna pita, dll)">
|
||||
<input type="text" name="request_khusus"
|
||||
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>
|
||||
<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>
|
||||
|
|
@ -45,24 +55,44 @@
|
|||
<div class="formulirbuket-card d-flex flex-column gap-3">
|
||||
<div>
|
||||
<label class="form-label small">Nama Lengkap</label>
|
||||
<input type="text" class="form-control formulirbuket-input"
|
||||
placeholder="Masukkan Nama Lengkap">
|
||||
<input type="text" name="nama"
|
||||
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>
|
||||
<label class="form-label small">Nomor WhatsApp</label>
|
||||
<input type="number" class="form-control formulirbuket-input"
|
||||
placeholder="Masukkan Nomor WhatsApp">
|
||||
<input type="number" name="no_wa"
|
||||
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 class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<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 class="col-md-6">
|
||||
<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>
|
||||
|
|
@ -74,7 +104,7 @@
|
|||
<div class="formulirbuket-payment-box">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<div class="formulirbuket-upload-area mb-2 text-center position-relative">
|
||||
<input type="file"
|
||||
class="position-absolute w-100 h-100 opacity-0 start-0 top-0 cursor-pointer"
|
||||
id="fileUpload">
|
||||
<div class="py-4">
|
||||
<input type="file" name="bukti_bayar" accept="image/*"
|
||||
class="position-absolute w-100 h-100 opacity-0 start-0 top-0 cursor-pointer @error('bukti_bayar') is-invalid @enderror"
|
||||
id="fileUpload" value="{{ old('bukti_bayar') }}">
|
||||
<div class="py-4" id="uploadPlaceholder">
|
||||
<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 x-small text-muted">Max. 2 MB</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@error('bukti_bayar')
|
||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||
@enderror
|
||||
<p class="x-small text-muted mb-4">
|
||||
*Note: Pastikan pembayaran sudah dilakukan sebelum mengunggah bukti, ya!
|
||||
</p>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
|
@ -155,4 +188,22 @@ class="btn formulirbuket-btn-cancel flex-fill">Batalkan</a>
|
|||
});
|
||||
});
|
||||
</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
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@
|
|||
|
||||
<div class="row justify-content-center mb-2">
|
||||
<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>
|
||||
<form action="{{ route('pesan.buket') }}" method="GET" class="w-100 d-flex justify-content-center">
|
||||
<div class="katalogbuket-search-wrapper w-100">
|
||||
<i class="bi bi-search search-icon"></i>
|
||||
|
||||
<input type="text" name="keyword" class="form-control katalogbuket-search-input"
|
||||
placeholder="Cari bungamu di sini..." value="{{ request('keyword') }}">
|
||||
<input type="text" name="keyword" id="input-search"
|
||||
class="form-control katalogbuket-search-input" placeholder="Cari bungamu di sini..."
|
||||
value="{{ request('keyword') }}" autocomplete="off">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -35,10 +36,14 @@
|
|||
|
||||
<div class="mb-4">
|
||||
<ul class="list-unstyled katalogbuket-category-list">
|
||||
<li><a href="#">Single Flowers <i class="bi bi-chevron-right"></i></a></li>
|
||||
<li><a href="#">Fresh Flowers <i 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="#">Artificial Flowers <i class="bi bi-chevron-right"></i></a></li>
|
||||
<li><a href="#" class="filter-cat" data-cat="single">Single Flowers <i
|
||||
class="bi bi-chevron-right"></i></a></li>
|
||||
<li><a href="#" class="filter-cat" data-cat="fresh">Fresh Flowers <i
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
|
@ -82,58 +87,56 @@
|
|||
aria-labelledby="headingSize">
|
||||
<div class="accordion-body px-0 pt-2">
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-size">S</button>
|
||||
<button class="btn btn-size">M</button>
|
||||
<button class="btn btn-size active">L</button>
|
||||
<button class="btn btn-size filter-size" data-size="S">S</button>
|
||||
<button class="btn btn-size filter-size" data-size="M">M</button>
|
||||
<button class="btn btn-size filter-size" data-size="L">L</button>
|
||||
</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 class="col-lg-9">
|
||||
<div class="row g-3">
|
||||
|
||||
@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>
|
||||
|
||||
@include('user._list')
|
||||
<div class="row mt-5">
|
||||
<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
|
||||
</a>
|
||||
@endif
|
||||
|
||||
{{-- Nomor Halaman --}}
|
||||
<div class="pagination-numbers text-muted">
|
||||
<span class="page-number active">1</span>
|
||||
<span class="page-number">2</span>
|
||||
<span class="page-number">3</span>
|
||||
<span class="page-number">...</span>
|
||||
<span class="page-number">10</span>
|
||||
@foreach ($buket->getUrlRange(1, $buket->lastPage()) as $page => $url)
|
||||
@if ($page == $buket->currentPage())
|
||||
<span class="page-number active">{{ $page }}</span>
|
||||
@else
|
||||
<a href="{{ $url }}"
|
||||
class="page-number text-decoration-none">{{ $page }}</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</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>
|
||||
</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>
|
||||
|
|
@ -142,53 +145,120 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
<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>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var slider = document.getElementById('price-slider');
|
||||
$(document).ready(function() {
|
||||
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, {
|
||||
start: [50000, 200000], // Posisi awal handle
|
||||
connect: true, // Ada warna di tengah handle
|
||||
start: [50000, 500000],
|
||||
connect: true,
|
||||
range: {
|
||||
'min': 0,
|
||||
'max': 500000
|
||||
'max': 1000000
|
||||
},
|
||||
step: 10000, // Kelipatan geser (10rb)
|
||||
format: {
|
||||
to: function(value) {
|
||||
return Math.round(value);
|
||||
},
|
||||
from: function(value) {
|
||||
return Number(value);
|
||||
}
|
||||
}
|
||||
step: 10000
|
||||
});
|
||||
|
||||
// Update Teks Saat 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');
|
||||
|
||||
// Update label dan input hidden saat slider digeser
|
||||
slider.noUiSlider.on('update', function(values, handle) {
|
||||
// Format Rupiah
|
||||
var formatter = new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0
|
||||
});
|
||||
var min = Math.round(values[0]);
|
||||
var max = Math.round(values[1]);
|
||||
|
||||
if (handle === 0) {
|
||||
minPriceText.innerHTML = formatter.format(values[0]);
|
||||
inputMin.value = values[0];
|
||||
} else {
|
||||
maxPriceText.innerHTML = formatter.format(values[1]);
|
||||
inputMax.value = values[1];
|
||||
}
|
||||
$('#price-min').text('Rp ' + min.toLocaleString('id-ID'));
|
||||
$('#price-max').text('Rp ' + max.toLocaleString('id-ID'));
|
||||
$('#input-min').val(min);
|
||||
$('#input-max').val(max);
|
||||
});
|
||||
});
|
||||
</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
|
||||
|
|
|
|||
Loading…
Reference in New Issue