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; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Buket;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\File;
class BuketController extends Controller class BuketController extends Controller
{ {
/**
* Display a listing of the resource.
*/
public function index() 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) 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) 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) 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 = [ protected $fillable = [
'nama', 'nama',
'deskripsi', 'deskripsi',
'request_khusus',
'ucapan',
'harga', 'harga',
'foto', 'foto',
'kategori', // Enum 'kategori', // Enum

View File

@ -15,6 +15,7 @@ public function up(): void
$table->id('id_buket'); $table->id('id_buket');
$table->string('nama'); $table->string('nama');
$table->text('deskripsi')->nullable(); $table->text('deskripsi')->nullable();
$table->text('request_khusus')->nullable();
$table->decimal('harga', 10, 2); // Format uang $table->decimal('harga', 10, 2); // Format uang
$table->string('foto')->nullable(); $table->string('foto')->nullable();
// Enum sesuai diskusi Wireframe & ERD // 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('title', 'Produk Buket')
@section('content') @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"> <section class="section">
<div class="buttons pb-3"> <div class="buttons pb-3">
<a href="#" class="btn-add-custom" data-bs-toggle="modal" data-bs-target="#create"> <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"> <table class="table table-striped" id="table1">
<thead> <thead>
<tr> <tr>
<th>No.</th> <th style="width: 5%">No.</th>
<th>Nama Buket</th> <th style="width: 20%">Nama Buket</th>
<th>Deskripsi</th> <th style="width: 35%">Deskripsi</th>
<th>Harga</th> <th style="width: 15%" class="text-nowrap">Harga</th>
<th>Foto</th> <th style="width: 10%">Foto</th>
<th class="text-center">Aksi</th> <th style="width: 15%" class="text-center">Aksi</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> @forelse ($buket as $b)
<td>Graiden</td> <tr>
<td>vehicula.aliquet@semconsequat.co.uk</td> <td style="width: 5%">{{ $loop->iteration }}</td>
<td>076 4820 8838</td> <td style="width: 20%">{{ $b->nama }}</td>
<td>Offenburg</td> <td style="width: 35%">{{ Str::limit($b->deskripsi, 50) }}</td>
<td>Offenburg</td> <td style="width: 15%">Rp {{ number_format($b->harga, 0, ',', '.') }}</td>
<td class="col-auto text-center"> <td style="width:10%">
<a href="#" class="btn icon btn-primary btn-action" data-bs-toggle="modal" <img src="{{ asset($b->foto) }}" alt="Foto Produk" class="rounded"
data-bs-target="#show"> style="width: 50px; height: 50px; object-fit: cover;">
<i class="bi bi-eye"></i> </td>
</a> <td class="col-auto text-center" style="width: 15%">
<a href="#" class="btn icon btn-warning btn-action" data-bs-toggle="modal" <a href="#" class="btn icon btn-primary btn-action" data-bs-toggle="modal"
data-bs-target="#edit"> data-bs-target="#show{{ $b->id_buket }}">
<i class="bi bi-pencil"></i> <i class="bi bi-eye"></i>
</a> </a>
<a href="#" class="btn icon btn-danger btn-action" data-bs-toggle="modal"
data-bs-target="#delete"> <a href="#" class="btn icon btn-warning btn-action" data-bs-toggle="modal"
<i class="bi bi-trash"></i> data-bs-target="#edit{{ $b->id_buket }}">
</a> <i class="bi bi-pencil"></i>
</td> </a>
</tr>
<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> </tbody>
</table> </table>
</div> </div>
@ -52,7 +81,86 @@
</section> </section>
@include('admin.produk-buket.partials.modal-create') @include('admin.produk-buket.partials.modal-create')
@include('admin.produk-buket.partials.modal-show') @push('scripts')
@include('admin.produk-buket.partials.modal-edit') <script>
@include('admin.produk-buket.partials.modal-delete') // 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 @endsection

View File

@ -1,76 +1,142 @@
<div class="modal fade" id="create" tabindex="-1" aria-hidden="true"> <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-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Tambah Produk Buket</h5> <button type="button" class="btn-close" <h5 class="modal-title">Tambah Produk Buket</h5>
data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </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="modal-body">
<div class="row gx-3"> <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"> <div class="mb-2">
<label class="form-label">Nama Buket</label> <label class="form-label">Nama Buket</label>
<input type="text" class="form-control" <input type="text" name="nama"
style="font-size: 13px;"placeholder="Masukkan Nama Buket"> 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> </div>
{{-- Harga Buket --}}
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Harga Buket</label> <label class="form-label">Harga Buket</label>
<input type="number" class="form-control" {{-- UPDATE 3: Tambah name="harga" --}}
style="font-size: 13px;"placeholder="Masukkan Harga Buket"> <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> </div>
{{-- Request Khusus --}}
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Request Khusus</label> <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"> placeholder="Masukkan Opsi Custom">
</div> </div>
{{-- Deskripsi Buket --}}
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Deskripsi Buket</label> <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> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-5">
<div class="mb-3">
<div class="mb-2">
<label class="form-label">Upload Foto Buket</label> <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> <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> <span class="fw-semibold text-dark">Klik untuk Upload</span>
<small class="text-muted">Max. 2 MB</small> <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> </div>
@error('foto')
<div class="text-danger mt-1" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div> </div>
<div class="mb-2"> {{-- 7. Preview Foto (Fixed Container) --}}
<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" {{-- Placeholder Teks (ID UNIK: createPlaceholder) --}}
style="height: 135px; background-color: #f8f9fa; overflow: hidden;"> <div id="createPlaceholder" class="text-center text-muted">
<i class="bi bi-image fs-4"></i>
<div id="placeholder-text" class="text-center text-muted"> <p class="mb-0" style="font-size: 12px;">Belum ada foto dipilih</p>
<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>
</div> </div>
<img id="img-preview" src="#" class="img-fluid w-100 h-100 d-none" {{-- Gambar Preview (ID UNIK: createImgPreview) --}}
style="object-fit: cover; position: absolute; top: 0; left: 0;"> <img id="createImgPreview" src="#" class="d-none w-100 h-100"
style="object-fit: contain;">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -84,33 +150,3 @@
</div> </div>
</div> </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-dialog modal-dialog-centered ">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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> data-bs-dismiss="modal" aria-label="Close"></button>
</div> </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"> <div class="modal-body">
<p>Apakah anda yakin ingin menghapus nama buket?</p> <p>Apakah anda yakin ingin menghapus {{ $b->nama }}?</p>
</div> </div>
<div class="modal-footer justify-content-end border-top-0 pt-0"> <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"> <button type="submit" class="btn btn-danger rounded-pill tolak px-3 py-2">
@ -21,33 +23,3 @@
</div> </div>
</div> </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 fade" id="edit{{ $b->id_buket }}" 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-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Edit Produk Buket</h5> <button type="button" class="btn-close" <h5 class="modal-title">Edit {{ $b->nama }}</h5>
data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </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="modal-body">
<div class="row gx-3"> <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"> <div class="mb-2">
<label class="form-label">Nama Buket</label> <label class="form-label">Nama Buket</label>
<input type="text" class="form-control" <input type="text" name="nama"
style="font-size: 13px;"placeholder="Masukkan Nama Buket"> 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>
<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"> <div class="mb-2">
<label class="form-label">Harga Buket</label> <label class="form-label">Harga Buket</label>
<input type="number" class="form-control" <input type="number" class="form-control @error('harga') is-invalid @enderror"
style="font-size: 13px;"placeholder="Masukkan Harga Buket"> 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> </div>
{{-- Request Khusus --}}
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Request Khusus</label> <label class="form-label">Request Khusus</label>
<input type="text" class="form-control"style="font-size: 13px;" <input type="text" class="form-control" name="request_khusus"
placeholder="Masukkan Opsi Custom"> value="{{ old('request_khusus', $b->request_khusus) }}" style="font-size: 14px;">
</div> </div>
{{-- Deskripsi --}}
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Deskripsi Buket</label> <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> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-5">
<div class="mb-3">
<div class="mb-2">
<label class="form-label">Upload Foto Buket</label> <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" <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> <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> <span class="fw-semibold text-dark">Klik untuk Upload</span>
<small class="text-muted">Max. 2 MB</small> <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> </div>
@error('foto')
<div class="text-danger mt-1" style="font-size: 12px;">{{ $message }}</div>
@enderror
</div> </div>
<div class="mb-2"> <div class="mb-3">
<label class="form-label">Preview Foto</label> <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" {{-- UPDATE: Menampilkan Foto Lama --}}
style="height: 135px; background-color: #f8f9fa; overflow: hidden;"> <div id="editPlaceholder{{ $b->id_buket }}" class="text-center text-muted d-none">
<i class="bi bi-image fs-4"></i>
<div id="placeholder-text" class="text-center text-muted"> <p class="mb-0" style="font-size: 12px;">Belum ada foto</p>
<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>
</div> </div>
<img id="img-preview" src="#" class="img-fluid w-100 h-100 d-none" <img id="editImgPreview{{ $b->id_buket }}" src="{{ asset($b->foto) }}"
style="object-fit: cover; position: absolute; top: 0; left: 0;"> class="w-100 h-100" style="object-fit: contain;">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -80,37 +136,6 @@
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </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-dialog modal-dialog-centered modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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> data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row gx-3"> <div class="row gx-3 mb-2">
<div class="col-12 col-md-4"> <div class="col-12 col-sm-4">
<div class="buket-img-wrapper text-align-center h-100" @if ($b->foto)
onclick="showImage('{{ asset('img/invoice.jpg') }}')"> {{-- Langsung img tanpa wrapper --}}
<img src="{{ asset('img/invoice.jpg') }}" class="proof-img"> <img src="{{ asset($b->foto) }}" class="custom-img-box"
</div> 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>
<div class="col-12 col-md-8"> <div class="col-12 col-sm-8">
<div class="detail-buket ps-md-3 h-100"> <div class="detail-buket ps-md-3">
<div class="row mb-2"> <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> <span class="detail-buket-label">Nama Buket</span>
</div> </div>
<div class="col-8 col-sm-9"> <div class="col-7 col-sm-8">
<span class="detail-buket-value">Buket Lily Premium</span> <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> </div>
<div class="row mb-2"> <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> <span class="detail-buket-label">Harga Buket</span>
</div> </div>
<div class="col-8 col-sm-9"> <div class="col-7 col-sm-8">
<span class="detail-buket-value">Rp 130.000</span> <span class="detail-buket-value">Rp
{{ number_format($b->harga, 0, ',', '.') }}</span>
</div> </div>
</div> </div>
<div class="row mb-2"> <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> <span class="detail-buket-label text-nowrap">Deskripsi Buket</span>
</div> </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;"> <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 {{ $b->deskripsi }}
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.
</p> </p>
</div> </div>
</div> </div>
<div class="row mb-2"> <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> <span class="detail-buket-label text-nowrap">Request Khusus</span>
</div> </div>
<div class="col-8 col-sm-9"> <div class="col-7 col-sm-8">
<span class="detail-buket-value">Wrapping, Tone Warna</span> <span class="detail-buket-value">{{ $b->request_khusus }}</span>
</div> </div>
</div> </div>
@ -66,33 +83,15 @@
</div> </div>
</div> </div>
</div> </div>
<script> <div class="modal fade" id="modalImagePreview" tabindex="-1" aria-hidden="true">
// Target elemen berdasarkan ID <div class="modal-dialog modal-dialog-centered modal-lg">
const fileInput = document.getElementById('fileInput'); <div class="modal-content bg-transparent border-0 shadow-none">
const imgPreview = document.getElementById('img-preview');
const placeholder = document.getElementById('placeholder-text');
fileInput.addEventListener('change', function(event) { <div class="modal-body p-0 text-center">
const file = event.target.files[0]; <img id="img-preview-target" src="" class="img-fluid rounded shadow-lg"
style="max-height: 85vh;">
</div>
if (file) { </div>
// Jika ada file, baca gambarnya </div>
const reader = new FileReader(); </div>
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>