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.
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
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!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class Buket extends Model
|
|||
protected $fillable = [
|
||||
'nama',
|
||||
'deskripsi',
|
||||
'request_khusus',
|
||||
'ucapan',
|
||||
'harga',
|
||||
'foto',
|
||||
'kategori', // Enum
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
|
@ -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>
|
||||
@forelse ($buket as $b)
|
||||
<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">
|
||||
<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">
|
||||
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">
|
||||
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">
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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 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 baby’s 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>
|
||||
|
|
|
|||