refactor: update file handling to use Storage facade and improve image path references

This commit is contained in:
LailaWulandarii 2026-01-08 19:00:52 +07:00
parent 192af00f2f
commit cd8a6b6a6e
9 changed files with 54 additions and 41 deletions

View File

@ -11,6 +11,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
class BookingFotoController extends Controller
{
@ -157,39 +158,46 @@ public function store(Request $request)
'nama' => 'required|string|min:3|max:100',
'no_wa' => 'required|numeric|digits_between:10,15',
'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048',
], [
// Detail Pesan Kustom
'required' => 'Kolom :attribute wajib diisi.',
'string' => 'Input :attribute harus berupa teks valid.',
'min' => ':attribute terlalu pendek, minimal :min karakter.',
'max' => ':attribute terlalu panjang, maksimal :max karakter.',
'numeric' => ':attribute harus berupa angka.',
'digits_between' => ':attribute harus antara :min sampai :max digit.',
'date' => 'Format tanggal pada :attribute tidak valid.',
'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.',
'image' => ':attribute harus berupa file gambar.',
'mimes' => 'Format :attribute harus jpeg, png, atau jpg.',
'max.file' => 'Ukuran :attribute maksimal adalah 2MB.',
], [
// Alias Atribut agar lebih ramah
'id_paket' => 'paket foto',
'nama' => 'nama pemesan',
'no_wa' => 'nomor WhatsApp',
'tgl_booking' => 'tanggal booking',
'jam_mulai' => 'jam mulai',
'bukti_bayar' => 'bukti pembayaran',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
\Illuminate\Support\Facades\DB::beginTransaction(); // Mulai Transaksi Database
DB::beginTransaction(); // Mulai Transaksi Database
try {
// 2. Ambil Data Paket & Hitung Waktu (Termasuk Tambahan Menit dari Addons)
$paket = \App\Models\PaketFoto::findOrFail($request->id_paket);
$totalDurasi = $paket->durasi;
// 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/foto'), $namaFile);
$pathBukti = $file->storeAs('img/payment/foto', $namaFile, 'public');
}
// 6. Hitung Grand Total (Paket + Additional)
// Kita hitung ulang di server agar aman dari manipulasi inspect element
$grandTotal = $paket->harga;
$listAdditional = [];

View File

@ -36,7 +36,7 @@ public function index(Request $request)
$buket = $query->paginate(8)->withQueryString();
if ($request->ajax()) {
return view('user._list', compact('buket'))->render();
return view('user.components._list', compact('buket'))->render();
}
return view('user/pesan-buket', compact('buket'));
@ -117,7 +117,7 @@ public function store(Request $request)
// Membuat nama file unik berdasarkan waktu agar tidak tertimpa
$namaFile = 'bukti_' . time() . $file->getClientOriginalExtension();
// Pindahkan ke folder public/img/payment
$file->move(public_path('img/payment/buket'), $namaFile);
$pathBukti = $file->storeAs('img/payment/buket', $namaFile, 'public');
}
$transaksi = TransaksiBuket::create([
@ -126,7 +126,7 @@ public function store(Request $request)
'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,
'bukti_bayar' => 'img/payment/buket' . $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-BUKET-' . strtoupper(\Illuminate\Support\Str::random(6)), // Tambahkan invoice sederhana

View File

@ -19,8 +19,8 @@ class Buket extends Model
'ucapan',
'harga',
'foto',
'kategori', // Enum
'ukuran', // Enum
'kategori',
'ukuran',
];
public function transaksi()

View File

@ -20,7 +20,7 @@
<div class="col">
<div class="bookingfoto-card position-relative">
<div class="img-wrapper mb-3">
<img src="{{ asset($f->foto) }}" class="img-fluid rounded-4"
<img src="{{ asset('storage/' . $f->foto) }}" class="img-fluid rounded-4"
alt="{{ $f->nama }}">
</div>
<div class="text-left">

View File

@ -4,7 +4,8 @@
<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 }}">
<img src="{{ asset('storage/' . $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>

View File

@ -18,7 +18,7 @@
<div class="col-lg-5 mb-5 mb-lg-0">
<div class="detailbuket-img-frame">
<img src="{{ asset($buket->foto) }}" class="img-fluid" alt="{{ $buket->nama }}">
<img src="{{ asset('storage/' . $buket->foto) }}" class="img-fluid" alt="{{ $buket->nama }}">
</div>
</div>

View File

@ -28,7 +28,7 @@
<div class="row gx-3">
<div class="col-auto">
<div class="detailfoto-card p-2">
<img src="{{ asset($foto->foto) }}" alt="{{ $foto->nama }}"
<img src="{{ asset('storage/' . $foto->foto) }}" alt="{{ $foto->nama }}"
class="detailfoto-thumb rounded-4">
</div>
</div>

View File

@ -22,7 +22,8 @@
<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($buket->foto) }}" alt="{{ $buket->nama }}" class="rounded-3">
<img src="{{ asset('storage/' . $buket->foto) }}" alt="{{ $buket->nama }}"
class="rounded-3">
<div>
<h6 class=" mb-1">{{ $buket->nama }}</h6>
<p class="text-teal mb-0">Rp {{ number_format($buket->harga, 0, ',', '.') }}</p>

View File

@ -147,6 +147,7 @@ class="page-number text-decoration-none">{{ $page }}</a>
</section>
<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).ready(function() {
let timeout = null;
@ -175,10 +176,12 @@ function fetchBuket(url = "{{ route('pesan.buket') }}") {
});
}
// Event live Search
$('#input-search').on('keyup', function() {
$(document).on('input', '#input-search', function() {
clearTimeout(timeout);
let keyword = $(this).val(); // Ambil value saat ini
timeout = setTimeout(function() {
// Opsional: Cek jika keyword kosong atau berubah
fetchBuket();
}, 500);
});