feat: Add new fields and functionalities for Buket products

- Added 'request_khusus' and 'ucapan' fields to the Buket model and migration.
- Updated the create and edit modals to include new fields for 'request_khusus' and 'ucapan'.
- Enhanced the product listing view with success and error alerts.
- Implemented image upload functionality with previews in create and edit modals.
- Improved the show modal to display detailed information about Buket products.
- Added multiple new images for Buket products in the public directory.
This commit is contained in:
LailaWulandarii 2025-12-28 19:21:16 +07:00
parent d4b9e5fdf0
commit a5ae9a66d8
22 changed files with 516 additions and 286 deletions

View File

@ -3,63 +3,150 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Buket;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\File;
class BuketController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('admin.produk-buket');
$buket = Buket::latest()->get();
return view('admin.produk-buket.index', compact('buket'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
$validator = Validator::make($request->all(), [
'nama' => 'required|string|min:3|max:100',
'ukuran' => 'required|in:S,M,L',
'kategori' => 'required|in:single,fresh,premium_fresh,artificial',
'harga' => 'required|numeric|min:0',
'request_khusus' => 'nullable|string|max:255',
'deskripsi' => 'required|string|min:10',
'foto' => 'required|image|mimes:jpeg,png,jpg|max:2048',
], [
// Detail Pesan Kustom menggunakan :attribute
'required' => 'Kolom :attribute tidak boleh kosong.',
'string' => 'Input :attribute harus berupa teks valid.',
'min' => ':attribute terlalu pendek, minimal harus :min karakter.',
'max' => ':attribute melebihi batas, maksimal :max karakter/KB.',
'in' => 'Pilihan :attribute tidak sesuai dengan data yang tersedia.',
'numeric' => ':attribute harus berupa angka.',
'image' => ':attribute harus berupa file gambar (foto).',
'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.',
'max.file' => 'Ukuran :attribute terlalu besar, maksimal adalah 2MB.',
], [
// Alias Atribut agar pesan lebih ramah pengguna
'nama' => 'nama buket',
'ukuran' => 'ukuran buket',
'kategori' => 'kategori buket',
'harga' => 'harga',
'request_khusus' => 'request khusus',
'deskripsi' => 'deskripsi produk',
'foto' => 'foto produk',
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput()
->with('error_modal', 'create');
}
$path = null;
if ($request->hasFile('foto')) {
$file = $request->file('foto');
$filename = time() . '_' . $file->getClientOriginalName();
$file->move(public_path('img/buket'), $filename);
$path = 'img/buket/' . $filename;
}
Buket::create([
'nama' => $request->nama,
'ukuran' => $request->ukuran,
'kategori' => $request->kategori,
'harga' => $request->harga,
'request_khusus' => $request->request_khusus,
'deskripsi' => $request->deskripsi,
'foto' => $path,
]);
return redirect()->back()->with('success', 'Produk buket berhasil ditambahkan!');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
$buket = Buket::findOrFail($id);
$validator = Validator::make($request->all(), [
'nama' => 'required|string|min:3|max:100',
'ukuran' => 'required|in:S,M,L',
'kategori' => 'required|in:single,fresh,premium_fresh,artificial',
'harga' => 'required|numeric|min:0',
'request_khusus' => 'nullable|string|max:255',
'deskripsi' => 'required|string|min:10',
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
], [
// Detail Pesan Kustom menggunakan :attribute
'required' => 'Kolom :attribute tidak boleh kosong.',
'string' => 'Input :attribute harus berupa teks valid.',
'min' => ':attribute terlalu pendek, minimal harus :min karakter.',
'max' => ':attribute melebihi batas, maksimal :max karakter/KB.',
'in' => 'Pilihan :attribute tidak sesuai dengan data yang tersedia.',
'numeric' => ':attribute harus berupa angka.',
'image' => ':attribute harus berupa file gambar (foto).',
'mimes' => 'Format :attribute tidak didukung. Gunakan format: jpeg, png, atau jpg.',
'max.file' => 'Ukuran :attribute terlalu besar, maksimal adalah 2MB.',
], [
// Alias Atribut agar pesan lebih ramah pengguna
'nama' => 'nama buket',
'ukuran' => 'ukuran buket',
'kategori' => 'kategori buket',
'harga' => 'harga',
'request_khusus' => 'request khusus',
'deskripsi' => 'deskripsi produk',
'foto' => 'foto produk',
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput()
->with('error_id', $id);
}
$data = $request->only(['nama', 'ukuran', 'kategori', 'harga', 'request_khusus', 'deskripsi']);
if ($request->hasFile('foto')) {
// 1. Hapus foto lama jika ada
if ($buket->foto && file_exists(public_path($buket->foto))) {
File::delete(public_path($buket->foto));
}
// 2. Upload foto baru
$file = $request->file('foto');
$filename = time() . '_' . $file->getClientOriginalName();
$file->move(public_path('img/buket'), $filename);
$data['foto'] = 'img/buket/' . $filename;
}
$buket->update($data);
return redirect()->back()->with('success', 'Produk buket berhasil diperbarui!');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
$buket = Buket::findOrFail($id);
if ($buket->foto && File::exists(public_path($buket->foto))) {
File::delete(public_path($buket->foto));
}
$buket->delete();
return redirect()->back()->with('success', 'Produk dan foto berhasil dihapus permanen!');
}
}

View File

@ -15,6 +15,8 @@ class Buket extends Model
protected $fillable = [
'nama',
'deskripsi',
'request_khusus',
'ucapan',
'harga',
'foto',
'kategori', // Enum

View File

@ -15,6 +15,7 @@ public function up(): void
$table->id('id_buket');
$table->string('nama');
$table->text('deskripsi')->nullable();
$table->text('request_khusus')->nullable();
$table->decimal('harga', 10, 2); // Format uang
$table->string('foto')->nullable();
// Enum sesuai diskusi Wireframe & ERD

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
public/img/buket/buket2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
public/img/buket/buket3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -3,6 +3,21 @@
@section('title', 'Produk Buket')
@section('content')
{{-- ALERT SUKSES --}}
@if (session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
{{-- ALERT ERROR UMUM (Jika ada error selain validasi modal) --}}
@if (session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<section class="section">
<div class="buttons pb-3">
<a href="#" class="btn-add-custom" data-bs-toggle="modal" data-bs-target="#create">
@ -15,36 +30,50 @@
<table class="table table-striped" id="table1">
<thead>
<tr>
<th>No.</th>
<th>Nama Buket</th>
<th>Deskripsi</th>
<th>Harga</th>
<th>Foto</th>
<th class="text-center">Aksi</th>
<th style="width: 5%">No.</th>
<th style="width: 20%">Nama Buket</th>
<th style="width: 35%">Deskripsi</th>
<th style="width: 15%" class="text-nowrap">Harga</th>
<th style="width: 10%">Foto</th>
<th style="width: 15%" class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
<tr>
<td>Graiden</td>
<td>vehicula.aliquet@semconsequat.co.uk</td>
<td>076 4820 8838</td>
<td>Offenburg</td>
<td>Offenburg</td>
<td class="col-auto text-center">
<a href="#" class="btn icon btn-primary btn-action" data-bs-toggle="modal"
data-bs-target="#show">
<i class="bi bi-eye"></i>
</a>
<a href="#" class="btn icon btn-warning btn-action" data-bs-toggle="modal"
data-bs-target="#edit">
<i class="bi bi-pencil"></i>
</a>
<a href="#" class="btn icon btn-danger btn-action" data-bs-toggle="modal"
data-bs-target="#delete">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
@forelse ($buket as $b)
<tr>
<td style="width: 5%">{{ $loop->iteration }}</td>
<td style="width: 20%">{{ $b->nama }}</td>
<td style="width: 35%">{{ Str::limit($b->deskripsi, 50) }}</td>
<td style="width: 15%">Rp {{ number_format($b->harga, 0, ',', '.') }}</td>
<td style="width:10%">
<img src="{{ asset($b->foto) }}" alt="Foto Produk" class="rounded"
style="width: 50px; height: 50px; object-fit: cover;">
</td>
<td class="col-auto text-center" style="width: 15%">
<a href="#" class="btn icon btn-primary btn-action" data-bs-toggle="modal"
data-bs-target="#show{{ $b->id_buket }}">
<i class="bi bi-eye"></i>
</a>
<a href="#" class="btn icon btn-warning btn-action" data-bs-toggle="modal"
data-bs-target="#edit{{ $b->id_buket }}">
<i class="bi bi-pencil"></i>
</a>
<a href="#" class="btn icon btn-danger btn-action" data-bs-toggle="modal"
data-bs-target="#delete{{ $b->id_buket }}">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
@include('admin.produk-buket.partials.modal-show')
@include('admin.produk-buket.partials.modal-edit')
@include('admin.produk-buket.partials.modal-delete')
@empty
<tr>
<td colspan="4" class="text-center text-muted">Tidak ada data buket.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@ -52,7 +81,86 @@
</section>
@include('admin.produk-buket.partials.modal-create')
@include('admin.produk-buket.partials.modal-show')
@include('admin.produk-buket.partials.modal-edit')
@include('admin.produk-buket.partials.modal-delete')
@push('scripts')
<script>
// LOGIC CLOSE MODAL
document.addEventListener('DOMContentLoaded', function() {
// Fungsi untuk membersihkan semua backdrop yang tertinggal
function clearBackdrop() {
const backdrops = document.querySelectorAll('.modal-backdrop');
backdrops.forEach(b => b.remove());
document.body.classList.remove('modal-open');
document.body.style.paddingRight = '';
}
// 1. Jika error di Modal Create
@if (session('error_modal') === 'create')
clearBackdrop(); // Bersihkan dulu
var createModal = new bootstrap.Modal(document.getElementById('create'));
createModal.show();
@endif
// 2. Jika error di Modal Edit (menggunakan session error_id)
@if (session('error_id'))
clearBackdrop(); // Bersihkan dulu
var editModalId = "edit{{ session('error_id') }}";
var editModal = new bootstrap.Modal(document.getElementById(editModalId));
editModal.show();
@endif
});
// LOGIC SHOW DETAIL IMG
function showImage(src) {
var modalImg = document.getElementById('img-preview-target');
modalImg.src = src;
var myModal = new bootstrap.Modal(document.getElementById('modalImagePreview'));
myModal.show();
}
// LOGIC PREVIEW EDIT IMG
function previewEditImage(event, id) {
const file = event.target.files[0];
const preview = document.getElementById('editImgPreview' + id);
const placeholder = document.getElementById('editPlaceholder' + id);
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.classList.remove('d-none');
placeholder.classList.add('d-none');
}
reader.readAsDataURL(file);
}
}
// LOGIC CREATE IMG
document.addEventListener('DOMContentLoaded', function() {
// Ambil elemen berdasarkan ID unik yang baru
const input = document.getElementById('createFotoInput');
const preview = document.getElementById('createImgPreview');
const placeholder = document.getElementById('createPlaceholder');
if (input) {
input.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.classList.remove('d-none'); // Munculkan gambar
placeholder.classList.add('d-none'); // Sembunyikan teks
}
reader.readAsDataURL(file);
} else {
// Reset kalau batal pilih
preview.src = "#";
preview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
});
}
});
</script>
@endpush
@endsection

View File

@ -1,76 +1,142 @@
<div class="modal fade" id="create" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg ">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tambah Produk Buket</h5> <button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title">Tambah Produk Buket</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="#" method="POST" enctype="multipart/form-data">
{{-- UPDATE 1: Action Route ke Store --}}
<form action="{{ route('admin.produk-buket.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="modal-body">
<div class="row gx-3">
<div class="col-12 col-md-7">
<div class="col-12 col-md-6">
{{-- Nama Buket --}}
<div class="mb-2">
<label class="form-label">Nama Buket</label>
<input type="text" class="form-control"
style="font-size: 13px;"placeholder="Masukkan Nama Buket">
<input type="text" name="nama"
class="form-control @error('nama') is-invalid @enderror" style="font-size: 14px;"
placeholder="Masukkan Nama Buket" value="{{ old('nama') }}">
@error('nama')
<div class="invalid-feedback" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div>
<div class="row">
{{-- Input Ukuran --}}
<div class="col-6 mb-2">
<label class="form-label">Ukuran</label>
<select class="form-select @error('ukuran') is-invalid @enderror" name="ukuran"
style="font-size: 14px;">
<option value="" selected disabled>Pilih...</option>
{{-- Value harus sama persis dengan enum di migrasi --}}
<option value="S" {{ old('ukuran') == 'S' ? 'selected' : '' }}>Small (S)
</option>
<option value="M" {{ old('ukuran') == 'M' ? 'selected' : '' }}>Medium (M)
</option>
<option value="L" {{ old('ukuran') == 'L' ? 'selected' : '' }}>Large (L)
</option>
</select>
</div>
{{-- Input Kategori --}}
<div class="col-6 mb-2">
<label class="form-label">Kategori</label>
<select class="form-select @error('kategori') is-invalid @enderror" name="kategori"
style="font-size: 14px;">
<option value="" selected disabled>Pilih...</option>
{{-- Sesuaikan value dengan: single, fresh, premium_fresh, artificial --}}
<option value="single" {{ old('kategori') == 'single' ? 'selected' : '' }}>
Single Flower</option>
<option value="fresh" {{ old('kategori') == 'fresh' ? 'selected' : '' }}>Fresh
Flower</option>
<option value="premium_fresh"
{{ old('kategori') == 'premium_fresh' ? 'selected' : '' }}>Premium Fresh
</option>
<option value="artificial"
{{ old('kategori') == 'artificial' ? 'selected' : '' }}>Artificial</option>
</select>
</div>
</div>
{{-- Harga Buket --}}
<div class="mb-2">
<label class="form-label">Harga Buket</label>
<input type="number" class="form-control"
style="font-size: 13px;"placeholder="Masukkan Harga Buket">
{{-- UPDATE 3: Tambah name="harga" --}}
<input type="number" class="form-control @error('harga') is-invalid @enderror"
name="harga" value="{{ old('harga') }}" style="font-size: 14px;"
placeholder="Masukkan Harga Buket">
@error('harga')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<p class="mb-0"><small class="text-muted mb-0">Dalam Rupiah</small>
</p>
</div>
{{-- Request Khusus --}}
<div class="mb-2">
<label class="form-label">Request Khusus</label>
<input type="text" class="form-control"style="font-size: 13px;"
{{-- UPDATE 4: Tambah name="request_khusus" --}}
<input type="text" class="form-control" name="request_khusus"
value="{{ old('request_khusus') }}" style="font-size: 14px;"
placeholder="Masukkan Opsi Custom">
</div>
{{-- Deskripsi Buket --}}
<div class="mb-2">
<label class="form-label">Deskripsi Buket</label>
<textarea class="form-control" rows="4" style="font-size: 13px;" placeholder="Masukkan Deskripsi Buket"></textarea>
{{-- UPDATE 5: Tambah name="deskripsi" --}}
<textarea class="form-control @error('deskripsi') is-invalid @enderror" name="deskripsi" rows="4"
style="font-size: 14px;" placeholder="Masukkan Deskripsi Buket">{{ old('deskripsi') }}</textarea>
@error('deskripsi')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-12 col-md-6">
<div class="mb-2">
<div class="col-12 col-md-5">
<div class="mb-3">
<label class="form-label">Upload Foto Buket</label>
<div class="upload-area p-4 text-center d-flex flex-column align-items-center justify-content-center"
onclick="document.getElementById('fileInput').click()">
{{-- Area Upload Custom --}}
<div class="upload-area p-4 text-centerd d-flex flex-column align-items-center justify-content-center"
onclick="document.getElementById('createFotoInput').click()"
style="cursor: pointer;">
<i class="bi bi-file-earmark-arrow-up fs-5 text-secondary mb-3"></i>
<span class="fw-semibold text-dark">Upload Foto Buket</span>
<small class="text-muted">Max. 2 MB</small>
<span class="fw-semibold text-dark">Klik untuk Upload</span>
<small class="text-muted">Max 2MB (JPG/PNG)</small>
<input type="file" id="fileInput" class="d-none" name="foto">
{{-- Input File Tersembunyi (ID UNIK: createFotoInput) --}}
<input type="file" id="createFotoInput" class="d-none" name="foto"
accept="image/*">
</div>
@error('foto')
<div class="text-danger mt-1" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div>
<div class="mb-2">
<label class="form-label">Preview Foto</label>
{{-- 7. Preview Foto (Fixed Container) --}}
<div class="mb-3">
<label class="form-label">Preview</label>
<div class="border rounded d-flex justify-content-center align-items-center position-relative overflow-hidden"
style="height: 200px; background-color: #f8f9fa;">
<div class="border rounded d-flex justify-content-center align-items-center position-relative"
style="height: 135px; background-color: #f8f9fa; overflow: hidden;">
<div id="placeholder-text" class="text-center text-muted">
<i class="bi bi-image fs-5 mb-2"></i>
<p class="mb-0 fw-medium" style="font-size: 0.65rem;">Gambarmu akan muncul di
sini</p>
{{-- Placeholder Teks (ID UNIK: createPlaceholder) --}}
<div id="createPlaceholder" class="text-center text-muted">
<i class="bi bi-image fs-4"></i>
<p class="mb-0" style="font-size: 12px;">Belum ada foto dipilih</p>
</div>
<img id="img-preview" src="#" class="img-fluid w-100 h-100 d-none"
style="object-fit: cover; position: absolute; top: 0; left: 0;">
{{-- Gambar Preview (ID UNIK: createImgPreview) --}}
<img id="createImgPreview" src="#" class="d-none w-100 h-100"
style="object-fit: contain;">
</div>
</div>
</div>
</div>
</div>
@ -84,33 +150,3 @@
</div>
</div>
</div>
<script>
// Target elemen berdasarkan ID
const fileInput = document.getElementById('fileInput');
const imgPreview = document.getElementById('img-preview');
const placeholder = document.getElementById('placeholder-text');
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
// Jika ada file, baca gambarnya
const reader = new FileReader();
reader.onload = function(e) {
imgPreview.src = e.target.result; // Masukkan data gambar
// TUKAR TAMPILAN:
imgPreview.classList.remove('d-none'); // Munculkan gambar
placeholder.classList.add('d-none'); // Sembunyikan teks
}
reader.readAsDataURL(file);
} else {
// Jika user membatalkan upload (cancel), reset ke awal
imgPreview.src = "#";
imgPreview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
});
</script>

View File

@ -1,15 +1,17 @@
<div class="modal fade" id="delete" tabindex="-1" aria-hidden="true">
<div class="modal fade" id="delete{{ $b->id_buket }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered ">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Hapus Produk Buket</h5> <button type="button" class="btn-close"
<h5 class="modal-title">Hapus {{ $b->nama }}</h5> <button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="#" method="POST" enctype="multipart/form-data">
<form action="{{ route('admin.produk-buket.destroy', $b->id_buket) }}" method="POST">
@csrf
@method('DELETE')
<div class="modal-body">
<p>Apakah anda yakin ingin menghapus nama buket?</p>
<p>Apakah anda yakin ingin menghapus {{ $b->nama }}?</p>
</div>
<div class="modal-footer justify-content-end border-top-0 pt-0">
<button type="submit" class="btn btn-danger rounded-pill tolak px-3 py-2">
@ -21,33 +23,3 @@
</div>
</div>
</div>
<script>
// Target elemen berdasarkan ID
const fileInput = document.getElementById('fileInput');
const imgPreview = document.getElementById('img-preview');
const placeholder = document.getElementById('placeholder-text');
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
// Jika ada file, baca gambarnya
const reader = new FileReader();
reader.onload = function(e) {
imgPreview.src = e.target.result; // Masukkan data gambar
// TUKAR TAMPILAN:
imgPreview.classList.remove('d-none'); // Munculkan gambar
placeholder.classList.add('d-none'); // Sembunyikan teks
}
reader.readAsDataURL(file);
} else {
// Jika user membatalkan upload (cancel), reset ke awal
imgPreview.src = "#";
imgPreview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
});
</script>

View File

@ -1,76 +1,132 @@
<div class="modal fade" id="edit" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal fade" id="edit{{ $b->id_buket }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg ">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Produk Buket</h5> <button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title">Edit {{ $b->nama }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="#" method="POST" enctype="multipart/form-data">
{{-- UPDATE: Route ke Update & Method PUT --}}
<form action="{{ route('admin.produk-buket.update', $b->id_buket) }}" method="POST"
enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="modal-body">
<div class="row gx-3">
<div class="col-12 col-md-7">
<div class="col-12 col-md-6">
{{-- Nama Buket --}}
<div class="mb-2">
<label class="form-label">Nama Buket</label>
<input type="text" class="form-control"
style="font-size: 13px;"placeholder="Masukkan Nama Buket">
<input type="text" name="nama"
class="form-control @error('nama') is-invalid @enderror" style="font-size: 14px;"
placeholder="Masukkan Nama Buket" value="{{ old('nama', $b->nama) }}">
@error('nama')
<div class="invalid-feedback" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div>
<div class="row">
{{-- Ukuran --}}
<div class="col-6 mb-2">
<label class="form-label">Ukuran</label>
<select class="form-select @error('ukuran') is-invalid @enderror" name="ukuran"
style="font-size: 14px;">
<option value="S" {{ old('ukuran', $b->ukuran) == 'S' ? 'selected' : '' }}>
Small (S)
</option>
<option value="M" {{ old('ukuran', $b->ukuran) == 'M' ? 'selected' : '' }}>
Medium (M)
</option>
<option value="L" {{ old('ukuran', $b->ukuran) == 'L' ? 'selected' : '' }}>
Large (L)
</option>
</select>
</div>
{{-- Kategori --}}
<div class="col-6 mb-2">
<label class="form-label">Kategori</label>
<select class="form-select @error('kategori') is-invalid @enderror" name="kategori"
style="font-size: 14px;">
<option value="single"
{{ old('kategori', $b->kategori) == 'single' ? 'selected' : '' }}>Single
Flower</option>
<option value="fresh"
{{ old('kategori', $b->kategori) == 'fresh' ? 'selected' : '' }}>Fresh
Flower</option>
<option value="premium_fresh"
{{ old('kategori', $b->kategori) == 'premium_fresh' ? 'selected' : '' }}>
Premium Fresh</option>
<option value="artificial"
{{ old('kategori', $b->kategori) == 'artificial' ? 'selected' : '' }}>
Artificial</option>
</select>
</div>
</div>
{{-- Harga --}}
<div class="mb-2">
<label class="form-label">Harga Buket</label>
<input type="number" class="form-control"
style="font-size: 13px;"placeholder="Masukkan Harga Buket">
<input type="number" class="form-control @error('harga') is-invalid @enderror"
name="harga" value="{{ old('harga', $b->harga) }}" style="font-size: 14px;">
<p class="mb-0"><small class="text-muted mb-0">Dalam Rupiah</small>
</p>
</div>
{{-- Request Khusus --}}
<div class="mb-2">
<label class="form-label">Request Khusus</label>
<input type="text" class="form-control"style="font-size: 13px;"
placeholder="Masukkan Opsi Custom">
<input type="text" class="form-control" name="request_khusus"
value="{{ old('request_khusus', $b->request_khusus) }}" style="font-size: 14px;">
</div>
{{-- Deskripsi --}}
<div class="mb-2">
<label class="form-label">Deskripsi Buket</label>
<textarea class="form-control" rows="4" style="font-size: 13px;" placeholder="Masukkan Deskripsi Buket"></textarea>
<textarea class="form-control @error('deskripsi') is-invalid @enderror" name="deskripsi" rows="4"
style="font-size: 14px;">{{ old('deskripsi', $b->deskripsi) }}</textarea>
</div>
</div>
<div class="col-12 col-md-6">
<div class="mb-2">
<div class="col-12 col-md-5">
<div class="mb-3">
<label class="form-label">Upload Foto Buket</label>
{{-- UPDATE: ID Input Unik --}}
<div class="upload-area p-4 text-center d-flex flex-column align-items-center justify-content-center"
onclick="document.getElementById('fileInput').click()">
onclick="document.getElementById('editFotoInput{{ $b->id_buket }}').click()"
style="cursor: pointer; border: 2px dashed #dee2e6; border-radius: 10px;">
<i class="bi bi-file-earmark-arrow-up fs-5 text-secondary mb-3"></i>
<span class="fw-semibold text-dark">Upload Foto Buket</span>
<small class="text-muted">Max. 2 MB</small>
<span class="fw-semibold text-dark">Klik untuk Upload</span>
<small class="text-muted">Max 2MB (JPG/PNG)</small>
<input type="file" id="fileInput" class="d-none" name="foto">
<input type="file" id="editFotoInput{{ $b->id_buket }}" class="d-none"
name="foto" accept="image/*"
onchange="previewEditImage(event, '{{ $b->id_buket }}')">
</div>
@error('foto')
<div class="text-danger mt-1" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div>
<div class="mb-2">
<label class="form-label">Preview Foto</label>
<div class="mb-3">
<label class="form-label">Preview</label>
<div class="border rounded d-flex justify-content-center align-items-center position-relative overflow-hidden"
style="height: 200px; background-color: #f8f9fa;">
<div class="border rounded d-flex justify-content-center align-items-center position-relative"
style="height: 135px; background-color: #f8f9fa; overflow: hidden;">
<div id="placeholder-text" class="text-center text-muted">
<i class="bi bi-image fs-5 mb-2"></i>
<p class="mb-0 fw-medium" style="font-size: 0.65rem;">Gambarmu akan muncul di
sini</p>
{{-- UPDATE: Menampilkan Foto Lama --}}
<div id="editPlaceholder{{ $b->id_buket }}" class="text-center text-muted d-none">
<i class="bi bi-image fs-4"></i>
<p class="mb-0" style="font-size: 12px;">Belum ada foto</p>
</div>
<img id="img-preview" src="#" class="img-fluid w-100 h-100 d-none"
style="object-fit: cover; position: absolute; top: 0; left: 0;">
<img id="editImgPreview{{ $b->id_buket }}" src="{{ asset($b->foto) }}"
class="w-100 h-100" style="object-fit: contain;">
</div>
</div>
</div>
</div>
</div>
@ -80,37 +136,6 @@
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Target elemen berdasarkan ID
const fileInput = document.getElementById('fileInput');
const imgPreview = document.getElementById('img-preview');
const placeholder = document.getElementById('placeholder-text');
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
// Jika ada file, baca gambarnya
const reader = new FileReader();
reader.onload = function(e) {
imgPreview.src = e.target.result; // Masukkan data gambar
// TUKAR TAMPILAN:
imgPreview.classList.remove('d-none'); // Munculkan gambar
placeholder.classList.add('d-none'); // Sembunyikan teks
}
reader.readAsDataURL(file);
} else {
// Jika user membatalkan upload (cancel), reset ke awal
imgPreview.src = "#";
imgPreview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
});
</script>

View File

@ -1,60 +1,77 @@
<div class="modal fade" id="show" tabindex="-1" aria-hidden="true">
<div class="modal fade" id="show{{ $b->id_buket }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Detail Nama Produk Bunga</h5> <button type="button" class="btn-close"
<h5 class="modal-title">Detail {{ $b->nama }}</h5> <button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row gx-3">
<div class="col-12 col-md-4">
<div class="buket-img-wrapper text-align-center h-100"
onclick="showImage('{{ asset('img/invoice.jpg') }}')">
<img src="{{ asset('img/invoice.jpg') }}" class="proof-img">
</div>
<div class="row gx-3 mb-2">
<div class="col-12 col-sm-4">
@if ($b->foto)
{{-- Langsung img tanpa wrapper --}}
<img src="{{ asset($b->foto) }}" class="custom-img-box"
onclick="showImage('{{ asset($b->foto) }}')">
@else
{{-- Div pengganti kalau tidak ada foto --}}
<div class="custom-img-box d-flex align-items-center justify-content-center text-muted">
<small>Tidak ada foto</small>
</div>
@endif
</div>
<div class="col-12 col-md-8">
<div class="detail-buket ps-md-3 h-100">
<div class="col-12 col-sm-8">
<div class="detail-buket ps-md-3">
<div class="row mb-2">
<div class="col-4 col-sm-3">
<div class="col-5 col-sm-4">
<span class="detail-buket-label">Nama Buket</span>
</div>
<div class="col-8 col-sm-9">
<span class="detail-buket-value">Buket Lily Premium</span>
<div class="col-7 col-sm-8">
<span class="detail-buket-value">{{ $b->nama }}</span>
</div>
</div>
<div class="row mb-2">
<div class="col-5 col-sm-4">
<span class="detail-buket-label text-nowrap">Ukuran & Kategori</span>
</div>
<div class="col-7 col-sm-8">
<div class="d-flex gap-2 flex-wrap">
<span
class="badge bg-success-subtle rounded-pill px-3 py-2 text-success">{{ $b->ukuran }}</span>
<span
class="badge bg-success-subtle rounded-pill px-3 py-2 text-success">{{ $b->kategori }}</span>
</div>
</div>
</div>
<div class="row mb-2">
<div class="col-4 col-sm-3">
<div class="col-5 col-sm-4">
<span class="detail-buket-label">Harga Buket</span>
</div>
<div class="col-8 col-sm-9">
<span class="detail-buket-value">Rp 130.000</span>
<div class="col-7 col-sm-8">
<span class="detail-buket-value">Rp
{{ number_format($b->harga, 0, ',', '.') }}</span>
</div>
</div>
<div class="row mb-2">
<div class="col-4 col-sm-3">
<div class="col-5 col-sm-4">
<span class="detail-buket-label text-nowrap">Deskripsi Buket</span>
</div>
<div class="col-8 col-sm-9">
<div class="col-7 col-sm-8">
<p class="detail-buket-value text-justify mb-0" style="line-height: 1.6;">
Buket Lily Premium menghadirkan keanggunan dalam setiap helai kelopak lily segar
berwarna putih atau pink, dipadukan dengan daun hijau dan sentuhan babys breath
yang lembut. Rangkaian ini dibalut kertas premium bernuansa nude atau ivory
dengan pita satin senada.
{{ $b->deskripsi }}
</p>
</div>
</div>
<div class="row mb-2">
<div class="col-4 col-sm-3">
<div class="col-5 col-sm-4">
<span class="detail-buket-label text-nowrap">Request Khusus</span>
</div>
<div class="col-8 col-sm-9">
<span class="detail-buket-value">Wrapping, Tone Warna</span>
<div class="col-7 col-sm-8">
<span class="detail-buket-value">{{ $b->request_khusus }}</span>
</div>
</div>
@ -66,33 +83,15 @@
</div>
</div>
</div>
<script>
// Target elemen berdasarkan ID
const fileInput = document.getElementById('fileInput');
const imgPreview = document.getElementById('img-preview');
const placeholder = document.getElementById('placeholder-text');
<div class="modal fade" id="modalImagePreview" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content bg-transparent border-0 shadow-none">
fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
<div class="modal-body p-0 text-center">
<img id="img-preview-target" src="" class="img-fluid rounded shadow-lg"
style="max-height: 85vh;">
</div>
if (file) {
// Jika ada file, baca gambarnya
const reader = new FileReader();
reader.onload = function(e) {
imgPreview.src = e.target.result; // Masukkan data gambar
// TUKAR TAMPILAN:
imgPreview.classList.remove('d-none'); // Munculkan gambar
placeholder.classList.add('d-none'); // Sembunyikan teks
}
reader.readAsDataURL(file);
} else {
// Jika user membatalkan upload (cancel), reset ke awal
imgPreview.src = "#";
imgPreview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
});
</script>
</div>
</div>
</div>