refactor: transaksi controller, cart dan filter wilayah lengkap

This commit is contained in:
sayasilvi 2026-03-02 02:52:24 +07:00
parent 69a3234d0b
commit f01c00fa87
29 changed files with 1827 additions and 992 deletions

3
.gitignore vendored
View File

@ -22,3 +22,6 @@
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
Thumbs.db Thumbs.db
/storage/framework/views/*.php
/storage/framework/cache/data/*
/storage/framework/sessions/*

View File

@ -141,14 +141,21 @@ ### Step 6: Database Migration
php artisan migrate --seed php artisan migrate --seed
``` ```
### Step 7: Konfigurasi Storage Link ### Step 7: Import Data Wilayah Indonesia (Wajib)
Jalankan perintah ini untuk mengisi data wilayah resmi Indonesia ke dalam database.
```bash
php artisan laravolt:indonesia:seed
```
### Step 8: Konfigurasi Storage Link
Jalankan perintah untuk konfigurasi storage link untuk menyimpan gambar Jalankan perintah untuk konfigurasi storage link untuk menyimpan gambar
```bash ```bash
php artisan storage:link php artisan storage:link
``` ```
### Step 8: Start Application ### Step 9: Start Application
**Terminal (Laravel Server):** **Terminal (Laravel Server):**
```bash ```bash
php artisan serve php artisan serve

View File

@ -12,15 +12,16 @@ class CartController extends Controller
// Menampilkan Halaman Keranjang // Menampilkan Halaman Keranjang
public function index() public function index()
{ {
// Cek Login Pembeli
if (!Auth::guard('pembeli')->check()) { if (!Auth::guard('pembeli')->check()) {
return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu untuk melihat keranjang.'); return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu untuk melihat keranjang.');
} }
$pembeli_id = Auth::guard('pembeli')->id(); $pembeli_id = Auth::guard('pembeli')->id();
// Ambil data keranjang dari Database $cart = Cart::with('produk')
$cart = Cart::with('produk')->where('pembeli_id', $pembeli_id)->get(); ->where('pembeli_id', $pembeli_id)
->latest()
->get();
return view('landing.cart', compact('cart')); return view('landing.cart', compact('cart'));
} }
@ -54,8 +55,8 @@ public function addToCart(Request $request)
} else { } else {
Cart::create([ Cart::create([
'pembeli_id' => $pembeli_id, 'pembeli_id' => $pembeli_id,
'produk_id' => $produk_id, 'produk_id' => $produk_id,
'quantity' => $quantity 'quantity' => $quantity
]); ]);
} }

View File

@ -25,11 +25,13 @@ public function index(Request $request)
return view('landing.partials.product_list', compact('produks'))->render(); return view('landing.partials.product_list', compact('produks'))->render();
} }
$produkTerlaris = Produk::withSum(['detailTransaksis as total_terjual' => function ($query) { $produkTerlaris = Produk::withSum([
$query->whereHas('transaksi', function ($q) { 'detailTransaksis as total_terjual' => function ($query) {
$q->where('status', '!=', 'batal'); $query->whereHas('transaksi', function ($q) {
}); $q->where('status', '!=', 'batal');
}], 'jumlah') });
}
], 'jumlah')
->orderByDesc('total_terjual') ->orderByDesc('total_terjual')
->take(4) ->take(4)
->get(); ->get();
@ -41,6 +43,21 @@ public function shop(Request $request)
{ {
$query = Produk::where('stok', '>', 0); $query = Produk::where('stok', '>', 0);
// --- FILTER LOKASI ---
if ($request->filled('provinsi')) {
$query->where('provinsi_code', $request->provinsi);
}
if ($request->filled('kota')) {
$query->where('kota_code', $request->kota);
}
if ($request->filled('kecamatan')) {
$query->where('kecamatan_code', $request->kecamatan);
}
if ($request->filled('desa')) {
$query->where('desa_code', $request->desa);
}
// Filter Search
if ($request->has('search') && $request->search != '') { if ($request->has('search') && $request->search != '') {
$query->where('nama_produk', 'like', '%' . $request->search . '%'); $query->where('nama_produk', 'like', '%' . $request->search . '%');
} }
@ -91,4 +108,4 @@ public function detail($id)
return view('landing.detail', compact('produk', 'produk_terkait')); return view('landing.detail', compact('produk', 'produk_terkait'));
} }
} }

View File

@ -6,38 +6,47 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Produk; use App\Models\Produk;
use App\Models\ProdukImage; use App\Models\ProdukImage;
use App\Models\Kategori; // PENTING: Import Model Kategori use App\Models\Kategori;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Laravolt\Indonesia\Models\City;
use Laravolt\Indonesia\Models\District;
use Laravolt\Indonesia\Models\Province;
use Laravolt\Indonesia\Models\Village;
class ProdukController extends Controller class ProdukController extends Controller
{ {
public function index() public function index()
{ {
$produks = Produk::with('kategori') $produks = Produk::with('kategori')
->where('petani_id', Auth::guard('petani')->id()) ->where('petani_id', Auth::guard('petani')->id())
->latest() ->latest()
->get(); ->get();
return view('petani.produk.index', compact('produks')); return view('petani.produk.index', compact('produks'));
} }
public function create() public function create()
{ {
$kategoris = Kategori::all(); $kategoris = Kategori::all();
$provinsis = Province::all();
return view('petani.produk.create', compact('kategoris'));
return view('petani.produk.create', compact('kategoris', 'provinsis'));
} }
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'nama_produk' => 'required|string|max:255', 'nama_produk' => 'required|string|max:255',
'kategori_id' => 'required|exists:kategoris,id', 'kategori_id' => 'required|exists:kategoris,id',
'harga' => 'required|numeric|min:0', 'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0', 'stok' => 'required|integer|min:0',
'deskripsi' => 'required|string', 'deskripsi' => 'required|string',
'foto_produk' => 'required|image|mimes:jpeg,png,jpg|max:2048', 'provinsi_code' => 'required',
'kota_code' => 'required',
'kecamatan_code' => 'required',
'desa_code' => 'required',
'foto_produk' => 'required|image|mimes:jpeg,png,jpg|max:2048',
'foto_tambahan.*' => 'nullable|image|mimes:jpeg,png,jpg|max:2048' 'foto_tambahan.*' => 'nullable|image|mimes:jpeg,png,jpg|max:2048'
]); ]);
@ -49,12 +58,16 @@ public function store(Request $request)
// Simpan Data Produk // Simpan Data Produk
$produk = Produk::create([ $produk = Produk::create([
'petani_id' => Auth::guard('petani')->id(), 'petani_id' => Auth::guard('petani')->id(),
'kategori_id' => $request->kategori_id, 'kategori_id' => $request->kategori_id,
'nama_produk' => $request->nama_produk, 'nama_produk' => $request->nama_produk,
'harga' => $request->harga, 'harga' => $request->harga,
'stok' => $request->stok, 'provinsi_code' => $request->provinsi_code,
'deskripsi' => $request->deskripsi, 'kota_code' => $request->kota_code,
'kecamatan_code' => $request->kecamatan_code,
'desa_code' => $request->desa_code,
'stok' => $request->stok,
'deskripsi' => $request->deskripsi,
'foto_produk' => $fotoPath, 'foto_produk' => $fotoPath,
]); ]);
@ -64,7 +77,7 @@ public function store(Request $request)
$path = $file->store('produk/gallery', 'public'); $path = $file->store('produk/gallery', 'public');
ProdukImage::create([ ProdukImage::create([
'produk_id' => $produk->id, 'produk_id' => $produk->id,
'foto' => $path 'foto' => $path
]); ]);
} }
} }
@ -77,7 +90,12 @@ public function edit($id)
$produk = Produk::with('images')->where('id', $id)->where('petani_id', Auth::guard('petani')->id())->firstOrFail(); $produk = Produk::with('images')->where('id', $id)->where('petani_id', Auth::guard('petani')->id())->firstOrFail();
$kategoris = Kategori::all(); $kategoris = Kategori::all();
return view('petani.produk.edit', compact('produk', 'kategoris')); $provinsis = Province::all();
$kotas = City::where('province_code', $produk->provinsi_code)->get();
$kecamatans = District::where('city_code', $produk->kota_code)->get();
$desas = Village::where('district_code', $produk->kecamatan_code)->get();
return view('petani.produk.edit', compact('produk', 'kategoris', 'provinsis', 'kotas', 'kecamatans', 'desas'));
} }
public function update(Request $request, $id) public function update(Request $request, $id)
@ -89,13 +107,17 @@ public function update(Request $request, $id)
$sisaSlot = max(3 - $jumlahGambarLama, 0); $sisaSlot = max(3 - $jumlahGambarLama, 0);
$request->validate([ $request->validate([
'nama_produk' => 'required|string|max:255', 'nama_produk' => 'required|string|max:255',
'kategori_id' => 'required|exists:kategoris,id', 'kategori_id' => 'required|exists:kategoris,id',
'harga' => 'required|numeric|min:0', 'harga' => 'required|numeric|min:0',
'stok' => 'required|integer|min:0', 'provinsi_code' => 'required',
'deskripsi' => 'required|string', 'kota_code' => 'required',
'foto_produk' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', 'kecamatan_code' => 'required',
'foto_tambahan' => 'array|max:' . $sisaSlot, 'desa_code' => 'required',
'stok' => 'required|integer|min:0',
'deskripsi' => 'required|string',
'foto_produk' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
'foto_tambahan' => 'array|max:' . $sisaSlot,
'foto_tambahan.*' => 'image|mimes:jpeg,png,jpg|max:2048' 'foto_tambahan.*' => 'image|mimes:jpeg,png,jpg|max:2048'
], [ ], [
'foto_tambahan.max' => "Anda hanya bisa menambah $sisaSlot foto lagi." 'foto_tambahan.max' => "Anda hanya bisa menambah $sisaSlot foto lagi."
@ -112,22 +134,27 @@ public function update(Request $request, $id)
// Update Data // Update Data
$produk->update([ $produk->update([
'nama_produk' => $request->nama_produk, 'nama_produk' => $request->nama_produk,
'kategori_id' => $request->kategori_id, 'kategori_id' => $request->kategori_id,
'harga' => $request->harga, 'harga' => $request->harga,
'stok' => $request->stok, 'provinsi_code' => $request->provinsi_code,
'deskripsi' => $request->deskripsi, 'kota_code' => $request->kota_code,
'foto_produk' => $produk->foto_produk 'kecamatan_code' => $request->kecamatan_code,
'desa_code' => $request->desa_code,
'stok' => $request->stok,
'deskripsi' => $request->deskripsi,
'foto_produk' => $produk->foto_produk
]); ]);
// Tambah Foto Galeri // Tambah Foto Galeri
if ($request->hasFile('foto_tambahan')) { if ($request->hasFile('foto_tambahan')) {
foreach ($request->file('foto_tambahan') as $file) { foreach ($request->file('foto_tambahan') as $file) {
if ($produk->images()->count() >= 3) break; if ($produk->images()->count() >= 3)
break;
$path = $file->store('produk/gallery', 'public'); $path = $file->store('produk/gallery', 'public');
ProdukImage::create([ ProdukImage::create([
'produk_id' => $produk->id, 'produk_id' => $produk->id,
'foto' => $path 'foto' => $path
]); ]);
} }
} }
@ -138,18 +165,18 @@ public function update(Request $request, $id)
public function destroy($id) public function destroy($id)
{ {
$produk = Produk::where('id', $id)->where('petani_id', Auth::guard('petani')->id())->firstOrFail(); $produk = Produk::where('id', $id)->where('petani_id', Auth::guard('petani')->id())->firstOrFail();
if ($produk->foto_produk && Storage::disk('public')->exists($produk->foto_produk)) { if ($produk->foto_produk && Storage::disk('public')->exists($produk->foto_produk)) {
Storage::disk('public')->delete($produk->foto_produk); Storage::disk('public')->delete($produk->foto_produk);
} }
foreach($produk->images as $img) { foreach ($produk->images as $img) {
if (Storage::disk('public')->exists($img->foto)) { if (Storage::disk('public')->exists($img->foto)) {
Storage::disk('public')->delete($img->foto); Storage::disk('public')->delete($img->foto);
} }
} }
$produk->images()->delete(); $produk->images()->delete();
$produk->delete(); $produk->delete();
return redirect()->route('petani.produk.index')->with('success', 'Produk berhasil dihapus.'); return redirect()->route('petani.produk.index')->with('success', 'Produk berhasil dihapus.');
@ -158,7 +185,7 @@ public function destroy($id)
public function deleteImage($id) public function deleteImage($id)
{ {
$image = ProdukImage::findOrFail($id); $image = ProdukImage::findOrFail($id);
if ($image->produk->petani_id != Auth::guard('petani')->id()) { if ($image->produk->petani_id != Auth::guard('petani')->id()) {
abort(403); abort(403);
} }

View File

@ -6,6 +6,7 @@
use App\Models\Produk; use App\Models\Produk;
use App\Models\Transaksi; use App\Models\Transaksi;
use App\Models\DetailTransaksi; use App\Models\DetailTransaksi;
use App\Models\Cart;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -13,10 +14,16 @@ class TransaksiController extends Controller
{ {
// --- FITUR PEMBELI --- // --- FITUR PEMBELI ---
// Tampilkan Halaman Checkout /**
* Tampilkan Halaman Checkout
* Mendukung Beli Langsung dan Checkout dari Keranjang (Database)
*/
public function checkoutPage(Request $request) public function checkoutPage(Request $request)
{ {
// Beli Langsung (Buy Now) // Mendefinisikan ID Pembeli agar tidak error 'Undefined Variable'
$pembeli_id = Auth::guard('pembeli')->id();
// 1. LOGIKA BELI LANGSUNG (Buy Now)
if ($request->has('produk_id')) { if ($request->has('produk_id')) {
$produk = Produk::with('petani')->findOrFail($request->produk_id); $produk = Produk::with('petani')->findOrFail($request->produk_id);
$items = collect([ $items = collect([
@ -34,32 +41,48 @@ public function checkoutPage(Request $request)
return view('landing.checkout', compact('items', 'total_belanja')); return view('landing.checkout', compact('items', 'total_belanja'));
} }
// Checkout dari Keranjang // 2. LOGIKA CHECKOUT DARI KERANJANG (Database)
$cart = session()->get('cart'); $cartIds = $request->query('cart_ids');
if ($cart && count($cart) > 0) {
$items = collect(); if (!$cartIds) {
foreach ($cart as $id => $details) { return redirect()->route('cart')->with('error', 'Pilih minimal satu produk di keranjang untuk dicheckout.');
$produk = Produk::find($id);
if ($produk) {
$items->push((object) [
'id' => $id,
'produk' => $produk,
'nama_produk' => $details['name'],
'harga' => $details['price'],
'jumlah' => $details['quantity'],
'subtotal' => $details['price'] * $details['quantity'],
'foto' => $details['photo']
]);
}
}
$total_belanja = $items->sum('subtotal');
return view('landing.checkout', compact('items', 'total_belanja'));
} }
return redirect()->route('shop')->with('error', 'Keranjang Anda kosong, silakan belanja dulu.'); $selectedIds = explode(',', $cartIds);
// Ambil data dari Tabel Cart yang ID-nya dicentang oleh user
$cartItems = Cart::with('produk.petani')
->where('pembeli_id', $pembeli_id)
->whereIn('id', $selectedIds)
->get();
if ($cartItems->isEmpty()) {
return redirect()->route('cart')->with('error', 'Barang di keranjang tidak ditemukan atau sudah dihapus.');
}
// Transformasi data agar sesuai dengan format yang dibaca di View Checkout
$items = $cartItems->map(function ($item) {
return (object) [
'cart_id' => $item->id,
'id' => $item->produk->id,
'produk' => $item->produk,
'nama_produk' => $item->produk->nama_produk,
'harga' => $item->produk->harga,
'jumlah' => $item->quantity,
'subtotal' => $item->produk->harga * $item->quantity,
'foto' => $item->produk->foto_produk
];
});
$total_belanja = $items->sum('subtotal');
return view('landing.checkout', compact('items', 'total_belanja'));
} }
// fungsi prosesCheckout /**
* Proses Pembuatan Transaksi
* Mengelompokkan pesanan berdasarkan Petani dan membersihkan keranjang
*/
public function prosesCheckout(Request $request) public function prosesCheckout(Request $request)
{ {
$request->validate([ $request->validate([
@ -70,9 +93,9 @@ public function prosesCheckout(Request $request)
$pembeli_id = Auth::guard('pembeli')->id(); $pembeli_id = Auth::guard('pembeli')->id();
DB::transaction(function () use ($request, $pembeli_id) { DB::transaction(function () use ($request, $pembeli_id) {
if ($request->has('produk_id')) { if ($request->has('produk_id')) {
// --- LOGIKA BELI LANGSUNG (Single Item) --- // --- LOGIKA BELI LANGSUNG ---
$produk = Produk::findOrFail($request->produk_id); $produk = Produk::findOrFail($request->produk_id);
$total_harga = $produk->harga * $request->jumlah; $total_harga = $produk->harga * $request->jumlah;
@ -97,65 +120,64 @@ public function prosesCheckout(Request $request)
$produk->decrement('stok', $request->jumlah); $produk->decrement('stok', $request->jumlah);
} else { } else {
// --- LOGIKA KERANJANG (Cart) --- // --- LOGIKA KERANJANG (DATABASE) ---
$cart = session()->get('cart'); if (!$request->cart_ids) {
throw new \Exception("ID keranjang tidak ditemukan.");
// Kelompokkan produk berdasarkan Petani ID
$cartItems = [];
foreach ($cart as $id => $details) {
$produk = Produk::find($id);
if ($produk) {
$cartItems[$produk->petani_id][] = [
'produk' => $produk,
'qty' => $details['quantity']
];
}
} }
foreach ($cartItems as $petani_id => $items) { $cartIds = explode(',', $request->cart_ids);
$subtotal_transaksi = 0;
$kode_invoice = 'INV/' . date('Ymd') . '/' . rand(1000, 9999); $cartItems = Cart::with('produk')
->whereIn('id', $cartIds)
->where('pembeli_id', $pembeli_id)
->get();
// Membuat Header Transaksi per Petani // Kelompokkan produk berdasarkan Petani ID agar Invoice terpisah per toko
$groupedByPetani = [];
foreach ($cartItems as $item) {
$groupedByPetani[$item->produk->petani_id][] = $item;
}
foreach ($groupedByPetani as $petani_id => $items) {
$transaksi = Transaksi::create([ $transaksi = Transaksi::create([
'pembeli_id' => $pembeli_id, 'pembeli_id' => $pembeli_id,
'petani_id' => $petani_id, 'petani_id' => $petani_id,
'tanggal_transaksi' => now(), 'tanggal_transaksi' => now(),
'alamat_pengiriman' => $request->alamat_pengiriman, 'alamat_pengiriman' => $request->alamat_pengiriman,
'total_harga' => 0, 'total_harga' => 0, // Diupdate setelah menghitung subtotal
'status' => 'menunggu konfirmasi', 'status' => 'menunggu konfirmasi',
'kode_invoice' => $kode_invoice, 'kode_invoice' => 'INV/' . date('Ymd') . '/' . rand(1000, 9999),
]); ]);
$subtotal_transaksi = 0;
foreach ($items as $item) { foreach ($items as $item) {
$produk = $item['produk']; $total_per_item = $item->produk->harga * $item->quantity;
$qty = $item['qty'];
$total_per_item = $produk->harga * $qty;
$subtotal_transaksi += $total_per_item; $subtotal_transaksi += $total_per_item;
DetailTransaksi::create([ DetailTransaksi::create([
'transaksi_id' => $transaksi->id, 'transaksi_id' => $transaksi->id,
'produk_id' => $produk->id, 'produk_id' => $item->produk->id,
'jumlah' => $qty, 'jumlah' => $item->quantity,
'harga_satuan' => $produk->harga, 'harga_satuan' => $item->produk->harga,
'subtotal' => $total_per_item, 'subtotal' => $total_per_item,
]); ]);
$produk->decrement('stok', $qty); $item->produk->decrement('stok', $item->quantity);
} }
// Update total harga transaksi // Update total harga transaksi per petani
$transaksi->update(['total_harga' => $subtotal_transaksi]); $transaksi->update(['total_harga' => $subtotal_transaksi]);
} }
session()->forget('cart'); // OTOMATIS BERSIHKAN ITEM KERANJANG YANG SUDAH DIBELI
Cart::whereIn('id', $cartIds)->delete();
} }
}); });
return redirect()->route('pesanan.saya')->with('success', 'Pesanan berhasil dibuat! Menunggu konfirmasi petani.'); return redirect()->route('pesanan.saya')->with('success', 'Pesanan berhasil dibuat! Silakan pantau status pesanan Anda.');
} }
// Riwayat Pesanan // Riwayat Pesanan Pembeli
public function pesananSaya() public function pesananSaya()
{ {
$transaksis = Transaksi::with(['detailTransaksis.produk.petani']) $transaksis = Transaksi::with(['detailTransaksis.produk.petani'])
@ -172,11 +194,10 @@ public function konfirmasiSelesai($id)
$transaksi = Transaksi::where('pembeli_id', Auth::guard('pembeli')->id()) $transaksi = Transaksi::where('pembeli_id', Auth::guard('pembeli')->id())
->findOrFail($id); ->findOrFail($id);
// Hanya bisa selesai jika status sebelumnya 'dikirim'
if ($transaksi->status == 'dikirim') { if ($transaksi->status == 'dikirim') {
$transaksi->status = 'selesai'; $transaksi->status = 'selesai';
$transaksi->save(); $transaksi->save();
return back()->with('success', 'Terima kasih! Pesanan telah diselesaikan.'); return back()->with('success', 'Terima kasih! Pesanan telah selesai.');
} }
return back()->with('error', 'Pesanan belum dikirim atau sudah selesai.'); return back()->with('error', 'Pesanan belum dikirim atau sudah selesai.');
@ -185,7 +206,7 @@ public function konfirmasiSelesai($id)
// --- FITUR PETANI --- // --- FITUR PETANI ---
// Daftar Pesanan Masuk // Daftar Pesanan Masuk untuk Dashboard Petani
public function pesananMasuk() public function pesananMasuk()
{ {
$petaniId = Auth::guard('petani')->id(); $petaniId = Auth::guard('petani')->id();
@ -200,7 +221,7 @@ public function pesananMasuk()
return view('petani.pesanan.index', compact('pesanans')); return view('petani.pesanan.index', compact('pesanans'));
} }
// Update Status (Terima/Tolak/Kirim) // Update Status Pesanan oleh Petani
public function updateStatus(Request $request, $id) public function updateStatus(Request $request, $id)
{ {
$transaksi = Transaksi::findOrFail($id); $transaksi = Transaksi::findOrFail($id);
@ -212,8 +233,9 @@ public function updateStatus(Request $request, $id)
$transaksi->status = $request->status; $transaksi->status = $request->status;
$transaksi->save(); $transaksi->save();
// Jika dibatalkan, kembalikan stok produk
if ($request->status == 'batal') { if ($request->status == 'batal') {
foreach ($transaksi->details as $detail) { foreach ($transaksi->detailTransaksis as $detail) {
$detail->produk->increment('stok', $detail->jumlah); $detail->produk->increment('stok', $detail->jumlah);
} }
} }
@ -221,12 +243,11 @@ public function updateStatus(Request $request, $id)
return back()->with('success', 'Status pesanan berhasil diperbarui.'); return back()->with('success', 'Status pesanan berhasil diperbarui.');
} }
// Detail Pesanan (Petani) // Detail Pesanan untuk Sisi Petani
public function pesananDetail($id) public function pesananDetail($id)
{ {
$petaniId = Auth::guard('petani')->id(); $petaniId = Auth::guard('petani')->id();
// Ambil transaksi berdasarkan ID
$pesanan = Transaksi::whereHas('detailTransaksis.produk', function ($q) use ($petaniId) { $pesanan = Transaksi::whereHas('detailTransaksis.produk', function ($q) use ($petaniId) {
$q->where('petani_id', $petaniId); $q->where('petani_id', $petaniId);
}) })
@ -235,4 +256,4 @@ public function pesananDetail($id)
return view('petani.pesanan.detail', compact('pesanan')); return view('petani.pesanan.detail', compact('pesanan'));
} }
} }

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laravolt\Indonesia\Models\City;
use Laravolt\Indonesia\Models\District;
use Laravolt\Indonesia\Models\Village;
class WilayahController extends Controller
{
public function getKota(Request $request)
{
$kota = City::where('province_code', $request->code)->get();
return response()->json($kota);
}
public function getKecamatan(Request $request)
{
$kecamatan = District::where('city_code', $request->code)->get();
return response()->json($kecamatan);
}
public function getDesa(Request $request)
{
$desa = Village::where('district_code', $request->code)->get();
return response()->json($desa);
}
}

View File

@ -17,7 +17,11 @@ class Produk extends Model
'harga', 'harga',
'stok', 'stok',
'deskripsi', 'deskripsi',
'foto_produk' 'foto_produk',
'provinsi_code',
'kota_code',
'kecamatan_code',
'desa_code',
]; ];
public function petani() public function petani()

View File

@ -8,7 +8,8 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1" "laravel/tinker": "^2.10.1",
"laravolt/indonesia": "^0.39.0"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",

84
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "c514d8f7b9fc5970bdd94287905ef584", "content-hash": "766a52f13bbfa552b799835b6f5f3e18",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -1457,6 +1457,88 @@
}, },
"time": "2025-01-27T14:24:01+00:00" "time": "2025-01-27T14:24:01+00:00"
}, },
{
"name": "laravolt/indonesia",
"version": "v0.39",
"source": {
"type": "git",
"url": "https://github.com/laravolt/indonesia.git",
"reference": "351e1c0e69b9415b0a56e03677fcfe0bbeca513a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravolt/indonesia/zipball/351e1c0e69b9415b0a56e03677fcfe0bbeca513a",
"reference": "351e1c0e69b9415b0a56e03677fcfe0bbeca513a",
"shasum": ""
},
"require": {
"illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0",
"php": "^7.3|^8.0"
},
"require-dev": {
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^9.0|^10.5|^11.5.3"
},
"suggest": {
"laravolt/suitable": "Required if you want to access editor panel",
"spatie/geocoder": "Synchronize latitude longitude data directly using Google's Geocoding Service"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Indonesia": "Laravolt\\Indonesia\\Facade"
},
"providers": [
"Laravolt\\Indonesia\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Laravolt\\Indonesia\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bayu Hendra Winata",
"email": "bayu.hendra@javan.co.id"
},
{
"name": "Akbar Adhatama",
"email": "am.adhatama@gmail.com"
},
{
"name": "Deri Ramdani",
"email": "deri.ramdani1@gmail.com"
}
],
"description": "Package Laravel yang berisi data Provinsi, Kabupaten/Kota, Kecamatan, dan Keluarahan/Desa di seluruh Indonesia.",
"keywords": [
"desa",
"indonesia",
"kabupaten",
"kecamatan",
"kelurahan",
"kota",
"laravel",
"laravolt",
"provinsi"
],
"support": {
"issues": "https://github.com/laravolt/indonesia/issues",
"source": "https://github.com/laravolt/indonesia/tree/v0.39"
},
"time": "2026-01-03T13:26:02+00:00"
},
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.7.1", "version": "2.7.1",

View File

@ -0,0 +1,24 @@
<?php
return [
'table_prefix' => 'indonesia_',
'route' => [
'enabled' => false,
'middleware' => ['web', 'auth'],
'prefix' => 'indonesia',
],
'view' => [
'layout' => 'ui::layouts.app',
],
'menu' => [
'enabled' => false,
],
'cache' => [
'ttl' => env('INDONESIA_CACHE_TTL', 3600),
'prefix' => env('INDONESIA_CACHE_PREFIX', 'indonesia_service'),
'store' => env('INDONESIA_CACHE_STORE', 'redis'),
],
'database' => [
'connection' => env('INDONESIA_DB_CONNECTION', null),
],
];

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProvincesTable extends Migration
{
protected function connection()
{
// New config (optional)
return config('indonesia.database.connection')
// Backward compatibility
?? config('database.default');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection($this->connection())->create(config('laravolt.indonesia.table_prefix').'provinces', function (Blueprint $table) {
$table->bigIncrements('id');
$table->char('code', 2)->unique();
$table->string('name', 255);
$table->text('meta')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop(config('laravolt.indonesia.table_prefix').'provinces');
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCitiesTable extends Migration
{
protected function connection()
{
// New config (optional)
return config('indonesia.database.connection')
// Backward compatibility
?? config('database.default');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection($this->connection())->create(config('laravolt.indonesia.table_prefix').'cities', function (Blueprint $table) {
$table->bigIncrements('id');
$table->char('code', 4)->unique();
$table->char('province_code', 2);
$table->string('name', 255);
$table->text('meta')->nullable();
$table->timestamps();
$table->foreign('province_code')
->references('code')
->on(config('laravolt.indonesia.table_prefix').'provinces')
->onUpdate('cascade')->onDelete('restrict');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop(config('laravolt.indonesia.table_prefix').'cities');
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDistrictsTable extends Migration
{
protected function connection()
{
// New config (optional)
return config('indonesia.database.connection')
// Backward compatibility
?? config('database.default');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection($this->connection())->create(config('laravolt.indonesia.table_prefix').'districts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->char('code', 7)->unique();
$table->char('city_code', 4);
$table->string('name', 255);
$table->text('meta')->nullable();
$table->timestamps();
$table->foreign('city_code')
->references('code')
->on(config('laravolt.indonesia.table_prefix').'cities')
->onUpdate('cascade')->onDelete('restrict');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop(config('laravolt.indonesia.table_prefix').'districts');
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateVillagesTable extends Migration
{
protected function connection()
{
// New config (optional)
return config('indonesia.database.connection')
// Backward compatibility
?? config('database.default');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection($this->connection())->create(config('laravolt.indonesia.table_prefix').'villages', function (Blueprint $table) {
$table->bigIncrements('id');
$table->char('code', 10)->unique();
$table->char('district_code', 7);
$table->string('name', 255);
$table->text('meta')->nullable();
$table->timestamps();
$table->foreign('district_code')
->references('code')
->on(config('laravolt.indonesia.table_prefix').'districts')
->onUpdate('cascade')->onDelete('restrict');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop(config('laravolt.indonesia.table_prefix').'villages');
}
}

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('produks', function (Blueprint $table) {
if (Schema::hasColumn('produks', 'lokasi')) {
$table->dropColumn('lokasi');
}
$table->char('provinsi_code', 2)->nullable()->after('deskripsi');
$table->char('kota_code', 4)->nullable()->after('provinsi_code');
$table->char('kecamatan_code', 7)->nullable()->after('kota_code');
$table->char('desa_code', 10)->nullable()->after('kecamatan_code');
});
}
public function down(): void
{
Schema::table('produks', function (Blueprint $table) {
$table->dropColumn(['provinsi_code', 'kota_code', 'kecamatan_code', 'desa_code']);
$table->string('lokasi')->nullable();
});
}
};

View File

@ -69,7 +69,7 @@ public function run(): void
'kode_invoice' => 'INV-' . $tahunIni . '03-003', 'kode_invoice' => 'INV-' . $tahunIni . '03-003',
'pembeli_id' => 1, 'pembeli_id' => 1,
'petani_id' => 1, 'petani_id' => 1,
'tanggal_transaksi' => Carbon::create($tahunIni, 3, 5, 9, 15, 0), 'tanggal_transaksi' => Carbon::create($tahunIni, 1, 5, 9, 15, 0),
'alamat_pengiriman' => 'Perumahan Indah Blok C2, Surabaya', 'alamat_pengiriman' => 'Perumahan Indah Blok C2, Surabaya',
'total_harga' => 150000, 'total_harga' => 150000,
'status' => 'selesai', 'status' => 'selesai',

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -79,6 +79,7 @@
<a href="{{ route('cart') }}" <a href="{{ route('cart') }}"
class="btn btn-outline-secondary py-2 rounded-pill">Kembali</a> class="btn btn-outline-secondary py-2 rounded-pill">Kembali</a>
</div> </div>
<input type="hidden" name="cart_ids" value="{{ request('cart_ids') }}">
</form> </form>
</div> </div>
</div> </div>

View File

@ -4,45 +4,39 @@
@section('content') @section('content')
<div class="container-fluid mb-5 bg-white"> <div class="container-fluid mb-5 bg-white">
<div class="container"> <div class="container">
<div class="row align-items-center g-5 py-5"> <div class="row align-items-center g-5 py-5">
<div class="col-lg-6 order-2 order-lg-1"> <div class="col-lg-6 order-2 order-lg-1">
<div class="d-inline-block border border-secondary text-secondary rounded-pill px-3 py-2 mb-3 fw-bold"> <div class="d-inline-block border border-secondary text-secondary rounded-pill px-3 py-2 mb-3 fw-bold">
🌾 Mitra Petani Padi Indonesia 🌾 Mitra Petani Padi Indonesia
</div>
<h1 class="display-4 fw-bold text-dark mb-4">
Pusat Jual Beli <br>
<span class="text-tani">Gabah Panen Raya.</span>
</h1>
<p class="lead text-muted mb-5">
Platform langsung yang menghubungkan petani padi lokal dengan pembeli.
Dapatkan harga gabah terbaik dan gabah berkualitas.
</p>
<div class="d-flex gap-2">
<a href="{{ route('shop') }}" class="btn btn-primary border-0 rounded-pill py-3 px-5 shadow">
<i class="fas fa-shopping-bag me-2"></i> Belanja
</a>
<a href="#katalog" class="btn btn-outline-secondary rounded-pill py-3 px-5">
Lihat Stok
</a>
</div>
</div> </div>
<div class="col-lg-6 order-1 order-lg-2 text-center"> <h1 class="display-4 fw-bold text-dark mb-4">
<div class="position-relative d-inline-block"> Pusat Jual Beli <br>
<img src="{{ asset('images/banner.jpg') }}" class="img-fluid rounded-circle shadow-lg w-100" <span class="text-tani">Gabah Panen Raya.</span>
style="max-height: 450px; object-fit: cover;" alt="Padi Premium"> </h1>
<div class="position-absolute bg-white p-3 rounded-3 shadow d-none d-md-block" <p class="lead text-muted mb-5">
style="bottom: 30px; left: -30px;"> Platform langsung yang menghubungkan petani padi lokal dengan pembeli.
<div class="d-flex align-items-center gap-3"> Dapatkan harga gabah terbaik dan gabah berkualitas.
<div class="bg-primary text-white rounded-circle p-2 d-flex align-items-center justify-content-center" </p>
style="width: 40px; height: 40px;"> <a href="{{ route('shop') }}" class="btn btn-primary border-0 rounded-pill py-3 px-5 shadow">
<i class="fas fa-check"></i> <i class="fas fa-shopping-bag me-2"></i> Belanja
</div> </a>
<div class="text-start"> </div>
<h6 class="mb-0 fw-bold">100% Panen Baru</h6> <div class="col-lg-6 order-1 order-lg-2 text-center">
<small class="text-muted">Kualitas Terjamin</small> <div class="position-relative d-inline-block">
</div> <img src="{{ asset('images/banner.jpg') }}" class="img-fluid rounded-circle shadow-lg w-100"
style="max-height: 450px; object-fit: cover;" alt="Padi Premium">
<div class="position-absolute bg-white p-3 rounded-3 shadow d-none d-md-block"
style="bottom: 30px; left: -30px;">
<div class="d-flex align-items-center gap-3">
<div class="bg-primary text-white rounded-circle p-2 d-flex align-items-center justify-content-center"
style="width: 40px; height: 40px;">
<i class="fas fa-check"></i>
</div>
<div class="text-start">
<h6 class="mb-0 fw-bold">100% Panen Baru</h6>
<small class="text-muted">Kualitas Terjamin</small>
</div> </div>
</div> </div>
</div> </div>
@ -50,197 +44,198 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="container py-5"> <div class="container py-5">
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm"> <div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm">
<div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4" <div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4"
style="width: 70px; height: 70px;"> style="width: 70px; height: 70px;">
<i class="fas fa-truck fa-2x"></i> <i class="fas fa-truck fa-2x"></i>
</div>
<h5 class="fw-bold mb-3">Siap Antar</h5>
<p class="mb-0 text-muted">Armada pickup siap kirim langsung ke lokasi Anda.</p>
</div> </div>
<h5 class="fw-bold mb-3">Siap Antar</h5>
<p class="mb-0 text-muted">Armada pickup siap kirim langsung ke lokasi Anda.</p>
</div> </div>
<div class="col-md-6 col-lg-3"> </div>
<div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm"> <div class="col-md-6 col-lg-3">
<div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4" <div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm">
style="width: 70px; height: 70px;"> <div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4"
<i class="fas fa-hand-holding-usd fa-2x"></i> style="width: 70px; height: 70px;">
</div> <i class="fas fa-hand-holding-usd fa-2x"></i>
<h5 class="fw-bold mb-3">Harga Petani</h5>
<p class="mb-0 text-muted">Harga tangan pertama langsung tanpa tengkulak.</p>
</div> </div>
<h5 class="fw-bold mb-3">Harga Petani</h5>
<p class="mb-0 text-muted">Harga tangan pertama langsung tanpa tengkulak.</p>
</div> </div>
<div class="col-md-6 col-lg-3"> </div>
<div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm"> <div class="col-md-6 col-lg-3">
<div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4" <div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm">
style="width: 70px; height: 70px;"> <div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4"
<i class="fas fa-certificate fa-2x"></i> style="width: 70px; height: 70px;">
</div> <i class="fas fa-certificate fa-2x"></i>
<h5 class="fw-bold mb-3">Kualitas Super</h5>
<p class="mb-0 text-muted">Jaminan padi berkualitas, bulir utuh, bersih, dan bebas campuran.</p>
</div> </div>
<h5 class="fw-bold mb-3">Kualitas Super</h5>
<p class="mb-0 text-muted">Jaminan padi berkualitas, bulir utuh, bersih, dan bebas campuran.</p>
</div> </div>
<div class="col-md-6 col-lg-3"> </div>
<div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm"> <div class="col-md-6 col-lg-3">
<div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4" <div class="h-100 p-4 text-center bg-white border rounded-4 shadow-sm">
style="width: 70px; height: 70px;"> <div class="d-inline-flex align-items-center justify-content-center bg-secondary text-white rounded-circle mb-4"
<i class="fas fa-shield-alt fa-2x"></i> style="width: 70px; height: 70px;">
</div> <i class="fas fa-shield-alt fa-2x"></i>
<h5 class="fw-bold mb-3">Transaksi Aman</h5>
<p class="mb-0 text-muted">Pembayaran fleksibel, bisa transfer atau COD.</p>
</div> </div>
<h5 class="fw-bold mb-3">Transaksi Aman</h5>
<p class="mb-0 text-muted">Pembayaran fleksibel, bisa transfer atau COD.</p>
</div> </div>
</div> </div>
</div> </div>
</div>
{{-- Produk terlaris --}} {{-- Produk terlaris --}}
<div class="container py-5"> <div class="container py-5">
<div class="text-center mx-auto mb-5" style="max-width: 700px;"> <div class="text-center mx-auto mb-5" style="max-width: 700px;">
<h1 class="display-6 fw-bold">Produk Terlaris</h1> <h1 class="display-6 fw-bold">Produk Terlaris</h1>
<p class="text-muted">Pilihan favorit pelanggan kami minggu ini.</p> <p class="text-muted">Pilihan favorit pelanggan kami minggu ini.</p>
</div> </div>
<div class="row g-4"> <div class="row g-4">
@forelse($produkTerlaris as $item) @forelse($produkTerlaris as $item)
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="rounded position-relative border border-secondary shadow-sm overflow-hidden h-100"> <div class="rounded position-relative border border-secondary shadow-sm overflow-hidden h-100">
<div class="fruit-img"> <div class="fruit-img">
<img src="{{ $item->foto_produk ? asset('storage/' . $item->foto_produk) : asset('template/frontend/img/fruite-item-5.jpg') }}" <img src="{{ $item->foto_produk ? asset('storage/' . $item->foto_produk) : asset('template/frontend/img/fruite-item-5.jpg') }}"
class="img-fluid w-100" style="height: 200px; object-fit: cover;" class="img-fluid w-100" style="height: 200px; object-fit: cover;"
alt="{{ $item->nama_produk }}"> alt="{{ $item->nama_produk }}">
</div> </div>
<div class="text-white bg-danger px-3 py-1 rounded position-absolute" style="top: 10px; left: 10px;"> <div class="text-white bg-danger px-3 py-1 rounded position-absolute" style="top: 10px; left: 10px;">
Terjual {{ $item->total_terjual ?? 0 }} Terjual {{ $item->total_terjual ?? 0 }}
</div> </div>
<div class="p-4 border-top border-secondary bg-white d-flex flex-column" <div class="p-4 border-top border-secondary bg-white d-flex flex-column"
style="height: calc(100% - 200px);"> style="height: calc(100% - 200px);">
<h4>{{ $item->nama_produk }}</h4> <h4>{{ $item->nama_produk }}</h4>
<p class="text-muted small mb-2 flex-grow-1"> <p class="text-muted small mb-2 flex-grow-1">
{{ Str::limit($item->deskripsi, 50) }} {{ Str::limit($item->deskripsi, 50) }}
</p>
<div class="d-flex justify-content-between align-items-end">
<div>
<p class="text-dark fs-5 fw-bold mb-0">Rp {{ number_format($item->harga, 0, ',', '.') }}
</p> </p>
<small class="text-muted">/ kg</small>
<div class="d-flex justify-content-between align-items-end">
<div>
<p class="text-dark fs-5 fw-bold mb-0">Rp {{ number_format($item->harga, 0, ',', '.') }}
</p>
<small class="text-muted">/ kg</small>
</div>
<a href="{{ route('produk.detail', $item->id) }}"
class="btn btn-outline-primary rounded-pill px-3">
<i class="fa fa-shopping-bag"></i>
</a>
</div>
</div> </div>
<a href="{{ route('produk.detail', $item->id) }}"
class="btn btn-outline-primary rounded-pill px-3">
<i class="fa fa-shopping-bag"></i>
</a>
</div> </div>
</div> </div>
@empty </div>
<div class="col-12 text-center"> </div>
<p class="text-muted">Belum ada data penjualan.</p> @empty
</div> <div class="col-12 text-center">
@endforelse <p class="text-muted">Belum ada data penjualan.</p>
</div>
@endforelse
</div>
</div>
<div class="container-fluid py-5 bg-light">
<div class="container">
<div class="row g-5">
<div class="col-md-6 col-lg-3 text-center">
<i class="fas fa-users fa-3x text-primary mb-3"></i>
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">150+</h2>
<p class="mb-0 text-muted text-uppercase fw-bold">Petani Mitra</p>
</div>
<div class="col-md-6 col-lg-3 text-center">
<i class="fas fa-shopping-cart fa-3x text-primary mb-3"></i>
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">2,500+</h2>
<p class="mb-0 text-muted text-uppercase fw-bold">Transaksi</p>
</div>
<div class="col-md-6 col-lg-3 text-center">
<i class="fas fa-star fa-3x text-primary mb-3"></i>
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">4.8</h2>
<p class="mb-0 text-muted text-uppercase fw-bold">Rating Rata-rata</p>
</div>
<div class="col-md-6 col-lg-3 text-center">
<i class="fas fa-check-circle fa-3x text-primary mb-3"></i>
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">100%</h2>
<p class="mb-0 text-muted text-uppercase fw-bold">Garansi Kualitas</p>
</div>
</div> </div>
</div> </div>
</div>
<div class="container-fluid py-5 bg-light"> {{-- Katalog --}}
<div class="container"> <div class="container py-5" id="katalog">
<div class="row g-5"> <div class="text-center mx-auto mb-5" style="max-width: 700px;">
<div class="col-md-6 col-lg-3 text-center"> <h2 class="fw-bold display-6 text-dark">Stok Gudang Kami</h2>
<i class="fas fa-users fa-3x text-primary mb-3"></i> <p class="text-muted">Pilih varietas gabah yang Anda butuhkan hari ini.</p>
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">150+</h2> </div>
<p class="mb-0 text-muted text-uppercase fw-bold">Petani Mitra</p>
</div> @php
<div class="col-md-6 col-lg-3 text-center"> $kategoriList = \App\Models\Kategori::all();
<i class="fas fa-shopping-cart fa-3x text-primary mb-3"></i> @endphp
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">2,500+</h2>
<p class="mb-0 text-muted text-uppercase fw-bold">Transaksi</p> <div class="row justify-content-center mb-5">
</div> <div class="col-12 text-center">
<div class="col-md-6 col-lg-3 text-center"> <div class="d-flex justify-content-center flex-wrap gap-2">
<i class="fas fa-star fa-3x text-primary mb-3"></i> <a href="#" data-kategori="Semua"
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">4.8</h2> class="btn {{ request('kategori') == '' || request('kategori') == 'Semua' ? 'btn-primary' : 'btn-outline-primary' }} rounded-pill px-4 py-2 m-1 btn-category">
<p class="mb-0 text-muted text-uppercase fw-bold">Rating Rata-rata</p> Semua
</div> </a>
<div class="col-md-6 col-lg-3 text-center">
<i class="fas fa-check-circle fa-3x text-primary mb-3"></i> {{-- 2. Looping Kategori dari Database --}}
<h2 class="fw-bold mb-0 text-secondary" data-toggle="counter-up">100%</h2> @foreach($kategoriList as $kat)
<p class="mb-0 text-muted text-uppercase fw-bold">Garansi Kualitas</p> <a href="#" data-kategori="{{ $kat->slug }}"
</div> class="btn {{ request('kategori') == $kat->slug ? 'btn-primary' : 'btn-outline-primary' }} rounded-pill px-4 py-2 m-1 btn-category">
<span>{{ $kat->nama_kategori }}</span>
</a>
@endforeach
</div> </div>
</div> </div>
</div> </div>
{{-- Katalog --}} <div class="row g-4" id="product-container">
<div class="container py-5" id="katalog"> @include('landing.partials.product_list')
<div class="text-center mx-auto mb-5" style="max-width: 700px;">
<h2 class="fw-bold display-6 text-dark">Stok Gudang Kami</h2>
<p class="text-muted">Pilih varietas gabah yang Anda butuhkan hari ini.</p>
</div>
@php
$kategoriList = \App\Models\Kategori::all();
@endphp
<div class="row justify-content-center mb-5">
<div class="col-12 text-center">
<div class="d-flex justify-content-center flex-wrap gap-2">
<a href="#" data-kategori="Semua"
class="btn {{ request('kategori') == '' || request('kategori') == 'Semua' ? 'btn-primary' : 'btn-outline-primary' }} rounded-pill px-4 py-2 m-1 btn-category">
Semua
</a>
{{-- 2. Looping Kategori dari Database --}}
@foreach($kategoriList as $kat)
<a href="#" data-kategori="{{ $kat->slug }}"
class="btn {{ request('kategori') == $kat->slug ? 'btn-primary' : 'btn-outline-primary' }} rounded-pill px-4 py-2 m-1 btn-category">
<span>{{ $kat->nama_kategori }}</span>
</a>
@endforeach
</div>
</div>
</div>
<div class="row g-4" id="product-container">
@include('landing.partials.product_list')
</div>
<div class="text-center mt-5">
<a href="{{ route('shop') }}" class="btn btn-primary border-0 rounded-pill px-5 py-3 shadow">
Lihat Semua Produk
</a>
</div>
</div> </div>
{{-- Banner --}} <div class="text-center mt-5">
<div class="container-fluid bg-primary py-5 mt-5"> <a href="{{ route('shop') }}" class="btn btn-primary border-0 rounded-pill px-5 py-3 shadow">
<div class="container py-5"> Lihat Semua Produk
<div class="row align-items-center"> </a>
<div class="col-lg-8 text-center text-lg-start mb-4 mb-lg-0"> </div>
<h2 class="display-5 fw-bold text-white mb-3">Butuh Suplai Besar?</h2> </div>
<p class="fs-5 text-white mb-4 opacity-75">
Kami melayani kerjasama untuk penggilingan padi, restoran, dan toko {{-- Banner --}}
kelontong dengan harga grosir spesial. <div class="container-fluid bg-primary py-5 mt-5">
</p> <div class="container py-5">
<a href="https://wa.me/6281234567890" target="_blank" <div class="row align-items-center">
class="btn btn-light rounded-pill px-5 py-3 text-success fw-bold shadow"> <div class="col-lg-8 text-center text-lg-start mb-4 mb-lg-0">
<i class="fab fa-whatsapp me-2"></i> Hubungi Kami <h2 class="display-5 fw-bold text-white mb-3">Butuh Suplai Besar?</h2>
</a> <p class="fs-5 text-white mb-4 opacity-75">
</div> Kami melayani kerjasama untuk penggilingan padi, restoran, dan toko
<div class="col-lg-4 text-center"> kelontong dengan harga grosir spesial.
<i class="fas fa-warehouse text-white opacity-25" style="font-size: 10rem;"></i> </p>
</div> <a href="https://wa.me/6281234567890" target="_blank"
class="btn btn-light rounded-pill px-5 py-3 text-success fw-bold shadow">
<i class="fab fa-whatsapp me-2"></i> Hubungi Kami
</a>
</div>
<div class="col-lg-4 text-center">
<i class="fas fa-warehouse text-white opacity-25" style="font-size: 10rem;"></i>
</div> </div>
</div> </div>
</div> </div>
</div>
@endsection @endsection
@section('js') @section('js')
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.btn-category').click(function (e) { $('.btn-category').click(function (e) {
e.preventDefault(); e.preventDefault();
@ -275,5 +270,5 @@ class="btn btn-light rounded-pill px-5 py-3 text-success fw-bold shadow">
}); });
}); });
}); });
</script> </script>
@endsection @endsection

View File

@ -5,17 +5,18 @@
<style> <style>
/* Layout Utama Chat */ /* Layout Utama Chat */
.chat-layout { .chat-layout {
height: 60vh; height: 60vh;
background-color: #fff; background-color: #fff;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
} }
/* --- Sidebar Kiri --- */ /* --- Sidebar Kiri --- */
.chat-sidebar { .chat-sidebar {
height: 100%; height: 100%;
background-color: #fff; background-color: #fff;
border-right: 1px solid #dee2e6; border-right: 1px solid #dee2e6;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -40,8 +41,8 @@
} }
.chat-item:hover { .chat-item:hover {
background-color: #f9fdf0; background-color: #f9fdf0;
border-left: 4px solid #81c408; border-left: 4px solid #81c408;
} }
/* --- Area Kanan --- */ /* --- Area Kanan --- */
@ -56,74 +57,92 @@
} }
/* Custom Scrollbar */ /* Custom Scrollbar */
::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar {
::-webkit-scrollbar-track { background: transparent; } width: 5px;
::-webkit-scrollbar-thumb { background: #ccc; border-radius: 10px; } }
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 10px;
}
</style> </style>
@endsection @endsection
@section('content') @section('content')
<div class="container py-5"> <div class="container py-5">
<h2 class="mb-4 fw-bold text-dark">Pesan Saya</h2> <h2 class="mb-4 fw-bold text-dark">Pesan Saya</h2>
{{-- Container Utama --}} {{-- Container Utama --}}
<div class="row g-0 chat-layout shadow-sm"> <div class="row g-0 chat-layout shadow-sm">
{{-- KOLOM KIRI: DAFTAR PESAN --}}
<div class="col-md-4 chat-sidebar">
<div class="sidebar-header">
<input type="text" class="form-control bg-light border-0 rounded-pill px-3" placeholder="Cari pesan...">
</div>
<div class="chat-list-container"> {{-- KOLOM KIRI: DAFTAR PESAN --}}
<div class="list-group list-group-flush"> <div class="col-md-4 chat-sidebar">
@forelse($chatList as $chat) {{-- Header Sidebar --}}
<a href="{{ route('pembeli.pesan.show', $chat['lawan_id']) }}" <div class="p-3 border-bottom">
class="list-group-item list-group-item-action chat-item py-3 border-0"> <h6 class="fw-bold text-dark mb-3">Pesan Masuk</h6>
<div class="d-flex align-items-center"> <input type="text" class="form-control bg-light border-0 rounded-pill px-3"
{{-- Avatar --}} placeholder="Cari percakapan...">
<div class="position-relative">
<img src="{{ asset('template/frontend/img/avatar.jpg') }}" class="rounded-circle shadow-sm"
width="50" height="50" style="object-fit: cover;">
@if ($chat['unread'] > 0)
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger border border-white">
{{ $chat['unread'] }}
</span>
@endif
</div>
{{-- Info Pesan --}}
<div class="ms-3 flex-grow-1 overflow-hidden">
<div class="d-flex justify-content-between align-items-center mb-1">
<h6 class="mb-0 text-dark fw-bold text-truncate">{{ $chat['nama'] }}</h6>
<small class="text-muted" style="font-size: 0.75rem;">{{ $chat['time'] }}</small>
</div>
<p class="mb-0 text-muted text-truncate small">
{{ Str::limit($chat['last_message'], 35) }}
</p>
</div>
</div>
</a>
@empty
<div class="text-center py-5 px-3">
<i class="far fa-comment-dots fa-3x text-muted mb-3 opacity-50"></i>
<p class="text-muted small">Belum ada percakapan.</p>
</div>
@endforelse
</div>
</div>
</div> </div>
{{-- KOLOM KANAN: PLACEHOLDER --}} {{-- List Chat --}}
<div class="col-md-8 d-none d-md-flex chat-placeholder"> <div class="flex-grow-1 overflow-auto">
<div class="text-center"> @forelse ($chatList as $chat)
<div class="bg-white p-4 rounded-circle shadow-sm d-inline-block mb-3"> <a href="{{ route('pembeli.pesan.show', $chat['lawan_id']) }}"
<i class="fas fa-comments fa-4x text-success"></i> class="d-flex align-items-center p-3 text-decoration-none text-dark chat-item">
<div class="position-relative">
<img src="{{ asset('template/frontend/img/avatar.jpg') }}" class="rounded-circle shadow-sm"
width="45" height="45" style="object-fit: cover;">
{{-- Badge Merah Notifikasi --}}
@if($chat['unread'] > 0)
<span
class="position-absolute top-0 start-100 translate-middle p-1 bg-danger border border-light rounded-circle"></span>
@endif
</div> </div>
<h5 class="fw-bold text-dark">Selamat Datang di Chat</h5>
<p class="small">Pilih salah satu percakapan di sebelah kiri<br>untuk mulai berdiskusi dengan Petani.</p> <div class="ms-3 flex-grow-1 overflow-hidden">
<div class="d-flex justify-content-between align-items-center">
<span class="fw-bold small">{{ $chat['nama'] }}</span>
<span class="text-muted" style="font-size: 0.7rem;">{{ $chat['time'] }}</span>
</div>
<div class="d-flex justify-content-between align-items-center mt-1">
<p class="mb-0 text-muted small text-truncate" style="max-width: 85%;">
{{ Str::limit($chat['last_message'], 25) }}
</p>
{{-- Angka Merah Notifikasi --}}
@if($chat['unread'] > 0)
<span class="badge bg-danger rounded-pill" style="font-size: 0.6rem;">{{ $chat['unread']
}}</span>
@endif
</div>
</div>
</a>
@empty
<div class="text-center py-5 px-3">
<i class="far fa-comment-dots fa-3x text-muted mb-3 opacity-50"></i>
<p class="text-muted small">Belum ada percakapan.</p>
</div> </div>
@endforelse
</div>
</div>
{{-- KOLOM KANAN: PLACEHOLDER --}}
<div class="col-md-8 d-none d-md-flex chat-placeholder">
<div class="text-center">
<div class="bg-white p-4 rounded-circle shadow-sm d-inline-block mb-3">
<i class="fas fa-comments fa-4x text-success"></i>
</div>
<h5 class="fw-bold text-dark">Selamat Datang di Chat</h5>
<p class="small">Pilih salah satu percakapan di sebelah kiri<br>untuk mulai berdiskusi dengan Petani.
</p>
</div> </div>
</div> </div>
</div> </div>
</div>
@endsection @endsection

View File

@ -107,6 +107,7 @@
@section('content') @section('content')
<div class="container py-5"> <div class="container py-5">
<h2 class="mb-4 fw-bold text-dark">Pesan Saya</h2>
<div class="row g-0 chat-layout shadow-lg"> <div class="row g-0 chat-layout shadow-lg">
{{-- SIDEBAR KIRI --}} {{-- SIDEBAR KIRI --}}

View File

@ -3,186 +3,268 @@
@section('title', 'Belanja Padi & Beras') @section('title', 'Belanja Padi & Beras')
@section('content') @section('content')
{{-- Header Halaman --}} {{-- Header Halaman --}}
<div class="container-fluid py-5 bg-light border-bottom mb-5"> <div class="container-fluid py-5 bg-light border-bottom mb-5">
<div class="container text-center"> <div class="container text-center">
<h1 class="display-5 fw-bold text-dark mb-3">Belanja Produk Kami</h1> <h1 class="display-5 fw-bold text-dark mb-3">Belanja Produk Kami</h1>
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-0"> <ol class="breadcrumb justify-content-center mb-0">
<li class="breadcrumb-item"><a href="{{ route('home') }}" class="text-decoration-none">Home</a></li> <li class="breadcrumb-item"><a href="{{ route('home') }}" class="text-decoration-none">Home</a></li>
<li class="breadcrumb-item active text-muted">Shop</li> <li class="breadcrumb-item active text-muted">Shop</li>
</ol> </ol>
</nav> </nav>
</div>
</div> </div>
</div>
{{-- Konten Utama --}} {{-- Konten Utama --}}
<div class="container pb-5"> <div class="container pb-5">
<div class="row g-4"> <div class="row g-4">
{{-- SIDEBAR FILTER --}} {{-- SIDEBAR FILTER --}}
<div class="col-lg-3"> <div class="col-lg-3">
{{-- Card Kategori --}} {{-- Filter Wilayah Lengkap--}}
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4"> <div class="card-body p-4">
<h6 class="fw-bold mb-3 text-uppercase small text-muted">Kategori</h6> <h6 class="fw-bold mb-3 text-uppercase small text-muted">Filter Wilayah Lengkap</h6>
<div class="d-flex flex-column gap-2"> <form action="{{ route('shop') }}" method="GET">
@foreach(request()->only(['search', 'kategori', 'sort']) as $key => $val)
@php <input type="hidden" name="{{ $key }}" value="{{ $val }}">
$kategoriList = \App\Models\Kategori::all(); @endforeach
$currentKat = request('kategori');
@endphp
{{-- Tombol Semua Kategori --}}
<a href="{{ route('shop', array_merge(request()->query(), ['kategori' => null])) }}"
class="d-flex justify-content-between align-items-center text-decoration-none {{ !$currentKat ? 'fw-bold text-primary' : 'text-secondary' }}">
<span>Semua Kategori</span>
</a>
{{-- Looping Kategori Database --}}
@foreach($kategoriList as $kat)
<a href="{{ route('shop', array_merge(request()->query(), ['kategori' => $kat->slug])) }}"
class="d-flex justify-content-between align-items-center text-decoration-none {{ $currentKat == $kat->slug ? 'fw-bold text-primary' : 'text-secondary' }}">
<span>{{ $kat->nama_kategori }}</span>
@if($currentKat == $kat->slug)
<i class="fas fa-check small"></i>
@endif
</a>
@endforeach
<div class="mb-2">
<label class="small fw-bold">Provinsi</label>
<select name="provinsi" id="filter_provinsi" class="form-select form-select-sm">
<option value="">Semua Provinsi</option>
@foreach(\Laravolt\Indonesia\Models\Province::all() as $prov)
<option value="{{ $prov->code }}" {{ request('provinsi')==$prov->code ? 'selected' : ''
}}>{{ $prov->name }}</option>
@endforeach
</select>
</div> </div>
</div>
</div>
{{-- Banner Info Kecil --}} <div class="mb-2">
<div class="card border-0 shadow-sm bg-primary text-white"> <label class="small fw-bold">Kota/Kabupaten</label>
<div class="card-body p-4"> <select name="kota" id="filter_kota" class="form-select form-select-sm" {{
<h5 class="fw-bold mb-2"><i class="fas fa-leaf me-2"></i>Produk Segar</h5> !request('provinsi') ? 'disabled' : '' }}>
<p class="small mb-0 opacity-75">Semua produk kami diambil langsung dari petani lokal terpercaya. <option value="">Semua Kota</option>
</p> </select>
</div>
<div class="mb-2">
<label class="small fw-bold">Kecamatan</label>
<select name="kecamatan" id="filter_kecamatan" class="form-select form-select-sm" {{
!request('kota') ? 'disabled' : '' }}>
<option value="">Semua Kecamatan</option>
</select>
</div>
<div class="mb-3">
<label class="small fw-bold">Desa</label>
<select name="desa" id="filter_desa" class="form-select form-select-sm" {{
!request('kecamatan') ? 'disabled' : '' }}>
<option value="">Semua Desa</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-sm w-100 rounded-pill shadow-sm">
<i class="fas fa-search me-1"></i> Cari Produk
</button>
</form>
</div>
</div>
{{-- Card Kategori --}}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<h6 class="fw-bold mb-3 text-uppercase small text-muted">Kategori</h6>
<div class="d-flex flex-column gap-2">
@php
$kategoriList = \App\Models\Kategori::all();
$currentKat = request('kategori');
@endphp
{{-- Tombol Semua Kategori --}}
<a href="{{ route('shop', array_merge(request()->query(), ['kategori' => null])) }}"
class="d-flex justify-content-between align-items-center text-decoration-none {{ !$currentKat ? 'fw-bold text-primary' : 'text-secondary' }}">
<span>Semua Kategori</span>
</a>
{{-- Looping Kategori Database --}}
@foreach($kategoriList as $kat)
<a href="{{ route('shop', array_merge(request()->query(), ['kategori' => $kat->slug])) }}"
class="d-flex justify-content-between align-items-center text-decoration-none {{ $currentKat == $kat->slug ? 'fw-bold text-primary' : 'text-secondary' }}">
<span>{{ $kat->nama_kategori }}</span>
@if($currentKat == $kat->slug)
<i class="fas fa-check small"></i>
@endif
</a>
@endforeach
</div> </div>
</div> </div>
</div> </div>
{{-- LIST PRODUK --}} {{-- Banner Info Kecil --}}
<div class="col-lg-9"> <div class="card border-0 shadow-sm bg-primary text-white">
<div class="card-body p-4">
<h5 class="fw-bold mb-2"><i class="fas fa-leaf me-2"></i>Produk Segar</h5>
<p class="small mb-0 opacity-75">Semua produk kami diambil langsung dari petani lokal terpercaya.
</p>
</div>
</div>
</div>
{{-- Top Bar (Info Jumlah & Sorting) --}} {{-- LIST PRODUK --}}
<div class="d-flex justify-content-between align-items-center mb-4 bg-white p-3 rounded shadow-sm border"> <div class="col-lg-9">
<div class="d-none d-md-block text-muted small">
Menampilkan <span class="fw-bold text-dark">{{ $produks->firstItem() ?? 0 }}-{{ $produks->lastItem() ?? 0 }}</span>
dari <span class="fw-bold text-dark">{{ $produks->total() }}</span> produk
</div>
<div class="d-flex align-items-center"> {{-- Top Bar (Info Jumlah & Sorting) --}}
<label class="small text-muted me-2 text-nowrap">Urutkan:</label> <div class="d-flex justify-content-between align-items-center mb-4 bg-white p-3 rounded shadow-sm border">
<form id="sortForm" action="{{ route('shop') }}" method="GET"> <div class="d-none d-md-block text-muted small">
{{-- Pertahankan query lain (search/kategori) saat sorting --}} Menampilkan <span class="fw-bold text-dark">{{ $produks->firstItem() ?? 0 }}-{{ $produks->lastItem()
@foreach(request()->except('sort') as $key => $value) ?? 0 }}</span>
<input type="hidden" name="{{ $key }}" value="{{ $value }}"> dari <span class="fw-bold text-dark">{{ $produks->total() }}</span> produk
@endforeach
<select name="sort" class="form-select form-select-sm border-0 bg-light fw-bold text-dark"
onchange="document.getElementById('sortForm').submit()"
style="width: 160px; cursor: pointer;">
<option value="terbaru" {{ request('sort') == 'terbaru' ? 'selected' : '' }}>Paling Baru
</option>
<option value="termurah" {{ request('sort') == 'termurah' ? 'selected' : '' }}>Harga Terendah
</option>
<option value="termahal" {{ request('sort') == 'termahal' ? 'selected' : '' }}>Harga Tertinggi
</option>
<option value="terlaris" {{ request('sort') == 'terlaris' ? 'selected' : '' }}>Paling Laris
</option>
</select>
</form>
</div>
</div> </div>
{{-- Grid Produk --}} <div class="d-flex align-items-center">
<div class="row g-4"> <label class="small text-muted me-2 text-nowrap">Urutkan:</label>
@forelse($produks as $produk) <form id="sortForm" action="{{ route('shop') }}" method="GET">
<div class="col-md-6 col-lg-4"> {{-- Pertahankan query lain (search/kategori) saat sorting --}}
<a href="{{ route('produk.detail', $produk->id) }}" class="text-decoration-none"> @foreach(request()->except('sort') as $key => $value)
<div class="card border-0 shadow-sm h-100 product-card"> <input type="hidden" name="{{ $key }}" value="{{ $value }}">
<div class="position-absolute top-0 end-0 m-2 z-1"> @endforeach
<span class="badge bg-warning text-dark">
Stok: {{ $produk->stok }} Kg
</span>
</div>
<div class="overflow-hidden" style="height: 200px;"> <select name="sort" class="form-select form-select-sm border-0 bg-light fw-bold text-dark"
<img src="{{ $produk->foto_produk ? asset('storage/' . $produk->foto_produk) : 'https://images.unsplash.com/photo-1586201375761-83865001e31c?q=80&w=800&auto=format&fit=crop' }}" onchange="document.getElementById('sortForm').submit()"
class="card-img-top w-100 h-100" alt="{{ $produk->nama_produk }}" style="width: 160px; cursor: pointer;">
style="object-fit: cover;"> <option value="terbaru" {{ request('sort')=='terbaru' ? 'selected' : '' }}>Paling Baru
</div> </option>
<option value="termurah" {{ request('sort')=='termurah' ? 'selected' : '' }}>Harga Terendah
</option>
<option value="termahal" {{ request('sort')=='termahal' ? 'selected' : '' }}>Harga Tertinggi
</option>
<option value="terlaris" {{ request('sort')=='terlaris' ? 'selected' : '' }}>Paling Laris
</option>
</select>
</form>
</div>
</div>
<div class="card-body d-flex justify-content-between flex-column p-3"> {{-- Grid Produk --}}
<div> <div class="row g-4">
<span class="badge bg-primary mb-2">{{ $produk->kategori->nama_kategori ?? 'Umum' }}</span> @forelse($produks as $produk)
<h6 class="fw-bold mb-2 text-dark">{{ $produk->nama_produk }}</h6> <div class="col-md-6 col-lg-4">
<p class="text-muted small mb-3">{{ Str::limit($produk->deskripsi, 60) }}</p> <a href="{{ route('produk.detail', $produk->id) }}" class="text-decoration-none">
</div> <div class="card border-0 shadow-sm h-100 product-card">
<div class="position-absolute top-0 end-0 m-2 z-1">
<span class="badge bg-warning text-dark">
Stok: {{ $produk->stok }} Kg
</span>
</div>
<div class="d-flex justify-content-between align-items-center pt-2 border-top"> <div class="overflow-hidden" style="height: 200px;">
<div> <img src="{{ $produk->foto_produk ? asset('storage/' . $produk->foto_produk) : 'https://images.unsplash.com/photo-1586201375761-83865001e31c?q=80&w=800&auto=format&fit=crop' }}"
<small class="text-muted">Harga/Kg</small> class="card-img-top w-100 h-100" alt="{{ $produk->nama_produk }}"
<h6 class="fw-bold text-primary mb-0"> style="object-fit: cover;">
Rp {{ number_format($produk->harga, 0, ',', '.') }} </div>
</h6>
</div> <div class="card-body d-flex justify-content-between flex-column p-3">
<button class="btn btn-primary btn-sm rounded-pill"> <div>
<i class="fa fa-eye"></i> Detail <span class="badge bg-primary mb-2">{{ $produk->kategori->nama_kategori ?? 'Umum'
</button> }}</span>
</div> <h6 class="fw-bold mb-2 text-dark">{{ $produk->nama_produk }}</h6>
</div> <p class="text-muted small mb-3">{{ Str::limit($produk->deskripsi, 60) }}</p>
</div>
<div class="d-flex justify-content-between align-items-center pt-2 border-top">
<div>
<small class="text-muted">Harga/Kg</small>
<h6 class="fw-bold text-primary mb-0">
Rp {{ number_format($produk->harga, 0, ',', '.') }}
</h6>
</div>
<button class="btn btn-primary btn-sm rounded-pill">
<i class="fa fa-eye"></i> Detail
</button>
</div> </div>
</a>
</div>
@empty
<div class="col-12">
<div class="text-center py-5 bg-white rounded-3 shadow-sm border border-dashed">
<i class="fas fa-search fa-3x text-muted opacity-50 mb-3"></i>
<h5 class="fw-bold text-dark">Produk Tidak Ditemukan</h5>
<p class="text-muted mb-4">Coba ubah kata kunci pencarian atau reset filter Anda.</p>
<a href="{{ route('shop') }}" class="btn btn-outline-primary rounded-pill px-4">
<i class="fas fa-sync-alt me-2"></i> Reset Filter
</a>
</div> </div>
</div> </div>
@endforelse </a>
</div> </div>
@empty
{{-- Pagination --}} <div class="col-12">
@if ($produks->hasPages()) <div class="text-center py-5 bg-white rounded-3 shadow-sm border border-dashed">
<div class="d-flex justify-content-center mt-5"> <i class="fas fa-search fa-3x text-muted opacity-50 mb-3"></i>
{{ $produks->withQueryString()->links() }} <h5 class="fw-bold text-dark">Produk Tidak Ditemukan</h5>
<p class="text-muted mb-4">Coba ubah kata kunci pencarian atau reset filter Anda.</p>
<a href="{{ route('shop') }}" class="btn btn-outline-primary rounded-pill px-4">
<i class="fas fa-sync-alt me-2"></i> Reset Filter
</a>
</div> </div>
@endif </div>
@endforelse
</div> </div>
{{-- Pagination --}}
@if ($produks->hasPages())
<div class="d-flex justify-content-center mt-5">
{{ $produks->withQueryString()->links() }}
</div>
@endif
</div> </div>
</div> </div>
</div>
<style> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
/* Hover Effect */ <script>
.product-card { $(document).ready(function() {
transition: all 0.3s ease; function setupCascading(source, target, routeName, placeholder) {
} $(source).on('change', function() {
let code = $(this).val();
$(target).html('<option value="">Memuat...</option>').prop('disabled', true);
if(target === '#filter_kota') {
$('#filter_kecamatan, #filter_desa').html('<option value="">Semua...</option>').prop('disabled', true);
}
.product-card:hover { if (code) {
transform: translateY(-5px); $.post("{{ url('/') }}/" + routeName, {code: code, _token: '{{ csrf_token() }}'}, function(data) {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1) !important; $(target).html('<option value="">' + placeholder + '</option>').prop('disabled', false);
} $.each(data, function(key, val) {
$(target).append(`<option value="${val.code}">${val.name}</option>`);
});
});
}
});
}
/* Zoom Image on Hover */ setupCascading('#filter_provinsi', '#filter_kota', 'get-kota', 'Semua Kota');
.product-card:hover img { setupCascading('#filter_kota', '#filter_kecamatan', 'get-kecamatan', 'Semua Kecamatan');
transform: scale(1.05); setupCascading('#filter_kecamatan', '#filter_desa', 'get-desa', 'Semua Desa');
} });
</script>
.overflow-hidden img {
transition: transform 0.5s ease; <style>
} /* Hover Effect */
</style> .product-card {
transition: all 0.3s ease;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1) !important;
}
/* Zoom Image on Hover */
.product-card:hover img {
transform: scale(1.05);
}
.overflow-hidden img {
transition: transform 0.5s ease;
}
</style>
@endsection @endsection

View File

@ -231,12 +231,7 @@
</a> </a>
</li> </li>
{{-- VERIFIKASI PETANI --}} {{-- MENU VERIFIKASI PETANI DIHAPUS SESUAI REVISI --}}
<li class="sidebar-item {{ request()->is('admin/verifikasi*') ? 'active' : '' }}">
<a href="{{ route('admin.verifikasi.index') }}" class='sidebar-link'>
<i class="bi bi-person-badge-fill"></i> <span>Verifikasi Petani</span>
</a>
</li>
{{-- GAPOKTAN --}} {{-- GAPOKTAN --}}
<li class="sidebar-item {{ request()->is('admin/gapoktan*') ? 'active' : '' }}"> <li class="sidebar-item {{ request()->is('admin/gapoktan*') ? 'active' : '' }}">
@ -256,6 +251,23 @@
{{-- Menu Dashboard Petani --}} {{-- Menu Dashboard Petani --}}
@if (Auth::guard('petani')->check()) @if (Auth::guard('petani')->check())
{{-- LOGIC UNTUK MENGHITUNG NOTIFIKASI BADGE MERAH --}}
@php
$petaniId = Auth::guard('petani')->id();
// Hitung pesanan dengan status 'menunggu konfirmasi'
$notifPesanan = \App\Models\Transaksi::where('petani_id', $petaniId)
->where('status', 'menunggu konfirmasi')
->count();
// Hitung chat yang belum dibaca (sudah_dibaca = false)
// Asumsi penerima_type menggunakan namespace model penuh 'App\Models\Petani'
$notifPesan = \App\Models\Pesan::where('penerima_id', $petaniId)
->where('penerima_type', 'App\Models\Petani')
->where('sudah_dibaca', false)
->count();
@endphp
<li class="sidebar-item {{ request()->is('petani/dashboard') ? 'active' : '' }}"> <li class="sidebar-item {{ request()->is('petani/dashboard') ? 'active' : '' }}">
<a href="{{ route('petani.dashboard') }}" class='sidebar-link'> <a href="{{ route('petani.dashboard') }}" class='sidebar-link'>
<i class="bi bi-grid-fill"></i> <span>Dashboard</span> <i class="bi bi-grid-fill"></i> <span>Dashboard</span>
@ -266,15 +278,28 @@
<i class="bi bi-basket-fill"></i> <span>Kelola Produk</span> <i class="bi bi-basket-fill"></i> <span>Kelola Produk</span>
</a> </a>
</li> </li>
{{-- Menu Pesanan Masuk (Dengan Badge) --}}
<li class="sidebar-item {{ request()->is('petani/pesanan*') ? 'active' : '' }}"> <li class="sidebar-item {{ request()->is('petani/pesanan*') ? 'active' : '' }}">
<a href="{{ route('petani.pesanan.index') }}" class='sidebar-link'> <a href="{{ route('petani.pesanan.index') }}" class='sidebar-link d-flex justify-content-between align-items-center'>
<i class="bi bi-receipt"></i> <span>Pesanan Masuk</span> <div>
<i class="bi bi-receipt"></i> <span>Pesanan Masuk</span>
</div>
@if($notifPesanan > 0)
<span class="badge bg-danger rounded-pill">{{ $notifPesanan }}</span>
@endif
</a> </a>
</li> </li>
<li
class="sidebar-item {{ request()->is('petani/pesan') || request()->is('petani/pesan/*') ? 'active' : '' }}"> {{-- Menu Kotak Masuk (Dengan Badge) --}}
<a href="{{ route('petani.pesan.index') }}" class='sidebar-link'> <li class="sidebar-item {{ request()->is('petani/pesan') || request()->is('petani/pesan/*') ? 'active' : '' }}">
<i class="bi bi-chat-dots-fill"></i> <span>Kotak Masuk</span> <a href="{{ route('petani.pesan.index') }}" class='sidebar-link d-flex justify-content-between align-items-center'>
<div>
<i class="bi bi-chat-dots-fill"></i> <span>Kotak Masuk</span>
</div>
@if($notifPesan > 0)
<span class="badge bg-danger rounded-pill">{{ $notifPesan }}</span>
@endif
</a> </a>
</li> </li>
@endif @endif

View File

@ -38,7 +38,12 @@
line-height: 1.6; line-height: 1.6;
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-weight: 700; font-weight: 700;
color: var(--dark-text); color: var(--dark-text);
@ -46,9 +51,17 @@
} }
/* Utility Colors */ /* Utility Colors */
.text-primary { color: var(--primary-color) !important; } .text-primary {
.bg-primary { background-color: var(--primary-color) !important; } color: var(--primary-color) !important;
.border-primary { border-color: var(--primary-color) !important; } }
.bg-primary {
background-color: var(--primary-color) !important;
}
.border-primary {
border-color: var(--primary-color) !important;
}
/* BUTTON */ /* BUTTON */
.btn { .btn {
@ -144,7 +157,8 @@
<body> <body>
<div id="spinner" class="show w-100 vh-100 bg-white position-fixed translate-middle top-50 start-50 d-flex align-items-center justify-content-center"> <div id="spinner"
class="show w-100 vh-100 bg-white position-fixed translate-middle top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"></div> <div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"></div>
</div> </div>
@ -153,7 +167,8 @@
<div class="container"> <div class="container">
<div class="d-flex justify-content-start text-white" style="font-size: 0.85rem;"> <div class="d-flex justify-content-start text-white" style="font-size: 0.85rem;">
<div> <div>
<span class="me-3"><i class="fas fa-map-marker-alt me-2"></i> Desa Mancon, Kecamatan Wilangan, Kabupaten Nganjuk</span> <span class="me-3"><i class="fas fa-map-marker-alt me-2"></i> Desa Mancon, Kecamatan Wilangan,
Kabupaten Nganjuk</span>
<span class="me-3"><i class="fas fa-envelope me-2"></i> info@tanidesa.com</span> <span class="me-3"><i class="fas fa-envelope me-2"></i> info@tanidesa.com</span>
</div> </div>
</div> </div>
@ -164,22 +179,27 @@
<div class="container"> <div class="container">
<nav class="navbar navbar-light navbar-expand-xl py-3"> <nav class="navbar navbar-light navbar-expand-xl py-3">
<a href="{{ url('/') }}" class="navbar-brand d-flex align-items-center"> <a href="{{ url('/') }}" class="navbar-brand d-flex align-items-center">
<h1 class="text-primary m-0 fw-bold" style="font-size: 1.8rem; letter-spacing: -1px;">GriyaPadi.id</h1> <h1 class="text-primary m-0 fw-bold" style="font-size: 1.8rem; letter-spacing: -1px;">
GriyaPadi.id</h1>
</a> </a>
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"> <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarCollapse">
<span class="fa fa-bars text-primary"></span> <span class="fa fa-bars text-primary"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto"> <div class="navbar-nav mx-auto">
<a href="{{ url('/') }}" class="nav-item nav-link mx-2 {{ request()->is('/') ? 'active' : '' }}">Home</a> <a href="{{ url('/') }}"
<a href="{{ route('shop') }}" class="nav-item nav-link mx-2 {{ request()->is('shop*') ? 'active' : '' }}">Belanja</a> class="nav-item nav-link mx-2 {{ request()->is('/') ? 'active' : '' }}">Home</a>
<a href="{{ route('shop') }}"
class="nav-item nav-link mx-2 {{ request()->is('shop*') ? 'active' : '' }}">Belanja</a>
</div> </div>
<div class="d-flex align-items-center mt-3 mt-xl-0"> <div class="d-flex align-items-center mt-3 mt-xl-0">
<form action="{{ route('shop') }}" method="GET" class="me-3 d-flex w-100 w-xl-auto mb-3 mb-xl-0"> <form action="{{ route('shop') }}" method="GET"
class="me-3 d-flex w-100 w-xl-auto mb-3 mb-xl-0">
<div class="input-group w-100"> <div class="input-group w-100">
<input type="search" name="search" class="form-control border-end-0" <input type="search" name="search" class="form-control border-end-0"
placeholder="Cari produk..." value="{{ request('search') }}" placeholder="Cari produk..." value="{{ request('search') }}"
@ -193,34 +213,64 @@
<a href="{{ route('cart') }}" class="position-relative me-3 text-dark"> <a href="{{ route('cart') }}" class="position-relative me-3 text-dark">
<i class="bi bi-bag fs-4"></i> <i class="bi bi-bag fs-4"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.6rem;"> <span
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"
style="font-size: 0.6rem;">
{{-- PERBAIKAN LOGIKA HITUNG CART --}} {{-- PERBAIKAN LOGIKA HITUNG CART --}}
@if(Auth::guard('pembeli')->check()) @if (Auth::guard('pembeli')->check())
{{ \App\Models\Cart::where('pembeli_id', Auth::guard('pembeli')->id())->count() }} {{ \App\Models\Cart::where('pembeli_id', Auth::guard('pembeli')->id())->count() }}
@else @else
0 0
@endif @endif
</span> </span>
</a> </a>
@if (Auth::guard('pembeli')->check()) @if (Auth::guard('pembeli')->check())
<div class="nav-item dropdown ms-2"> <div class="nav-item dropdown ms-2">
<a href="#" class="nav-link dropdown-toggle text-dark fw-bold d-flex align-items-center" data-bs-toggle="dropdown"> <a href="#"
<div class="bg-light rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 35px; height: 35px;"> class="nav-link dropdown-toggle text-dark fw-bold d-flex align-items-center"
data-bs-toggle="dropdown">
<div class="bg-light rounded-circle d-flex align-items-center justify-content-center me-2"
style="width: 35px; height: 35px;">
<i class="fas fa-user text-primary"></i> <i class="fas fa-user text-primary"></i>
</div> </div>
<span class="d-none d-xl-inline small">{{ Auth::guard('pembeli')->user()->nama_lengkap }}</span> <span
class="d-none d-xl-inline small">{{ Auth::guard('pembeli')->user()->nama_lengkap }}</span>
</a> </a>
<div class="dropdown-menu dropdown-menu-end border-0 shadow-sm m-0 rounded-3"> <div class="dropdown-menu dropdown-menu-end border-0 shadow-sm m-0 rounded-3">
<a href="{{ route('pembeli.profile') }}" class="dropdown-item py-2"><i class="bi bi-person me-2"></i> Profil</a> <a href="{{ route('pembeli.profile') }}" class="dropdown-item py-2"><i
<a href="{{ route('pembeli.pesan.index') }}" class="dropdown-item py-2"><i class="bi bi-chat-dots me-2"></i> Pesan</a> class="bi bi-person me-2"></i> Profil</a>
<a href="{{ route('pesanan.saya') }}" class="dropdown-item py-2"><i class="bi bi-bag-check me-2"></i> Riwayat Pesanan</a> <a href="{{ route('pembeli.pesan.index') }}"
class="dropdown-item py-2 d-flex justify-content-between align-items-center">
<span><i class="bi bi-chat-dots me-2"></i> Pesan</span>
{{-- Logika Badge Notifikasi Pembeli --}}
@if (Auth::guard('pembeli')->check())
@php
$notifPesanPembeli = \App\Models\Pesan::where(
'penerima_id',
Auth::guard('pembeli')->id(),
)
->where('penerima_type', 'App\Models\Pembeli')
->where('sudah_dibaca', false)
->count();
@endphp
@if ($notifPesanPembeli > 0)
<span class="badge bg-danger rounded-pill"
style="font-size: 0.7rem;">{{ $notifPesanPembeli }}</span>
@endif
@endif
</a>
<a href="{{ route('pesanan.saya') }}" class="dropdown-item py-2"><i
class="bi bi-bag-check me-2"></i> Riwayat Pesanan</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<form action="{{ route('logout') }}" method="POST"> <form action="{{ route('logout') }}" method="POST">
@csrf @csrf
<button type="submit" class="dropdown-item py-2 text-danger"><i class="bi bi-box-arrow-right me-2"></i> Logout</button> <button type="submit" class="dropdown-item py-2 text-danger"><i
class="bi bi-box-arrow-right me-2"></i> Logout</button>
</form> </form>
</div> </div>
</div> </div>
@ -237,10 +287,12 @@
<div style="margin-top: 170px;"> <div style="margin-top: 170px;">
@if (session('success')) @if (session('success'))
<div class="container mt-4"> <div class="container mt-4">
<div class="alert alert-success border-0 shadow-sm rounded-3 d-flex align-items-center" role="alert"> <div class="alert alert-success border-0 shadow-sm rounded-3 d-flex align-items-center"
role="alert">
<i class="bi bi-check-circle-fill fs-4 me-3"></i> <i class="bi bi-check-circle-fill fs-4 me-3"></i>
<div>{{ session('success') }}</div> <div>{{ session('success') }}</div>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"
aria-label="Close"></button>
</div> </div>
</div> </div>
@endif @endif
@ -253,27 +305,37 @@
<div class="row g-5"> <div class="row g-5">
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">GriyaPadi.id</h3> <h3 class="text-white mb-4">GriyaPadi.id</h3>
<p class="mb-4 small">Menghubungkan petani lokal langsung dengan pembeli untuk harga yang adil dan produk berkualitas tinggi.</p> <p class="mb-4 small">Menghubungkan petani lokal langsung dengan pembeli untuk harga yang adil dan
produk berkualitas tinggi.</p>
<div class="d-flex pt-2"> <div class="d-flex pt-2">
<a class="btn btn-outline-light btn-sm rounded-circle me-2" href=""><i class="fab fa-twitter"></i></a> <a class="btn btn-outline-light btn-sm rounded-circle me-2" href=""><i
<a class="btn btn-outline-light btn-sm rounded-circle me-2" href=""><i class="fab fa-facebook-f"></i></a> class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-sm rounded-circle" href=""><i class="fab fa-youtube"></i></a> <a class="btn btn-outline-light btn-sm rounded-circle me-2" href=""><i
class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-sm rounded-circle" href=""><i
class="fab fa-youtube"></i></a>
</div> </div>
</div> </div>
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6">
<h5 class="text-white mb-4">Tautan Cepat</h5> <h5 class="text-white mb-4">Tautan Cepat</h5>
<div class="d-flex flex-column justify-content-start"> <div class="d-flex flex-column justify-content-start">
<a class="text-white-50 mb-2 text-decoration-none" href="{{ route('shop') }}"><i class="bi bi-chevron-right me-2 small"></i>Belanja</a> <a class="text-white-50 mb-2 text-decoration-none" href="{{ route('shop') }}"><i
<a class="text-white-50 mb-2 text-decoration-none" href="#"><i class="bi bi-chevron-right me-2 small"></i>Tentang Kami</a> class="bi bi-chevron-right me-2 small"></i>Belanja</a>
<a class="text-white-50 text-decoration-none" href="#"><i class="bi bi-chevron-right me-2 small"></i>Hubungi Kami</a> <a class="text-white-50 mb-2 text-decoration-none" href="#"><i
class="bi bi-chevron-right me-2 small"></i>Tentang Kami</a>
<a class="text-white-50 text-decoration-none" href="#"><i
class="bi bi-chevron-right me-2 small"></i>Hubungi Kami</a>
</div> </div>
</div> </div>
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6">
<h5 class="text-white mb-4">Akun Saya</h5> <h5 class="text-white mb-4">Akun Saya</h5>
<div class="d-flex flex-column justify-content-start"> <div class="d-flex flex-column justify-content-start">
<a class="text-white-50 mb-2 text-decoration-none" href="{{ route('pembeli.profile') }}"><i class="bi bi-chevron-right me-2 small"></i>Profil</a> <a class="text-white-50 mb-2 text-decoration-none" href="{{ route('pembeli.profile') }}"><i
<a class="text-white-50 mb-2 text-decoration-none" href="{{ route('cart') }}"><i class="bi bi-chevron-right me-2 small"></i>Keranjang</a> class="bi bi-chevron-right me-2 small"></i>Profil</a>
<a class="text-white-50 text-decoration-none" href="{{ route('pesanan.saya') }}"><i class="bi bi-chevron-right me-2 small"></i>Riwayat Pesanan</a> <a class="text-white-50 mb-2 text-decoration-none" href="{{ route('cart') }}"><i
class="bi bi-chevron-right me-2 small"></i>Keranjang</a>
<a class="text-white-50 text-decoration-none" href="{{ route('pesanan.saya') }}"><i
class="bi bi-chevron-right me-2 small"></i>Riwayat Pesanan</a>
</div> </div>
</div> </div>
<div class="col-lg-3 col-md-6"> <div class="col-lg-3 col-md-6">
@ -286,12 +348,15 @@
</div> </div>
<div class="container-fluid copyright bg-dark py-4 border-top border-secondary"> <div class="container-fluid copyright bg-dark py-4 border-top border-secondary">
<div class="container text-center"> <div class="container text-center">
<span class="text-white-50 small">&copy; <a href="#" class="text-white">GriyaPadi.id</a>, All Right Reserved.</span> <span class="text-white-50 small">&copy; <a href="#" class="text-white">GriyaPadi.id</a>, All
Right
Reserved.</span>
</div> </div>
</div> </div>
</div> </div>
<a href="#" class="btn btn-primary btn-lg-square rounded-circle back-to-top shadow"><i class="bi bi-arrow-up"></i></a> <a href="#" class="btn btn-primary btn-lg-square rounded-circle back-to-top shadow"><i
class="bi bi-arrow-up"></i></a>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
@ -304,4 +369,4 @@
@yield('js') @yield('js')
</body> </body>
</html> </html>

View File

@ -4,232 +4,343 @@
@section('page-title', 'Tambah Produk Baru') @section('page-title', 'Tambah Produk Baru')
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<form action="{{ route('petani.produk.store') }}" method="POST" enctype="multipart/form-data"> <form action="{{ route('petani.produk.store') }}" method="POST" enctype="multipart/form-data">
@csrf @csrf
<div class="row"> <div class="row">
{{-- DATA PRODUK --}} {{-- DATA PRODUK --}}
<div class="col-lg-8"> <div class="col-lg-8">
<h5 class="mb-4">Informasi Produk</h5> <h5 class="mb-4">Informasi Produk</h5>
<div class="form-group mb-3"> <div class="form-group mb-3">
<label class="fw-bold">Nama Produk</label> <label class="fw-bold">Nama Produk</label>
<input type="text" name="nama_produk" class="form-control" <input type="text" name="nama_produk" class="form-control"
placeholder="Contoh: Beras Pandan Wangi Super" required> placeholder="Contoh: Beras Pandan Wangi Super" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="fw-bold">Kategori</label>
<select name="kategori_id" class="form-select" required>
<option value="" disabled selected>Pilih Kategori</option>
@foreach($kategoris as $kat)
<option value="{{ $kat->id }}" {{ old('kategori_id')==$kat->id ? 'selected' : ''
}}>
{{ $kat->nama_kategori }}
</option>
@endforeach
</select>
</div> </div>
<div class="col-md-6 mb-3">
<div class="row"> <label class="fw-bold">Stok (Kg)</label>
<div class="col-md-6 mb-3"> <input type="number" name="stok" class="form-control" placeholder="0" required
<label class="fw-bold">Kategori</label> min="0">
<select name="kategori_id" class="form-select" required>
<option value="" disabled selected>Pilih Kategori</option>
@foreach($kategoris as $kat)
<option value="{{ $kat->id }}" {{ old('kategori_id') == $kat->id ? 'selected' : '' }}>
{{ $kat->nama_kategori }}
</option>
@endforeach
</select>
</div>
<div class="col-md-6 mb-3">
<label class="fw-bold">Stok (Kg)</label>
<input type="number" name="stok" class="form-control" placeholder="0" required
min="0">
</div>
</div>
<div class="form-group mb-3">
<label class="fw-bold">Harga (Rp)</label>
<input type="number" name="harga" class="form-control" placeholder="0" required min="0">
</div>
<div class="form-group mb-3">
<label class="fw-bold">Deskripsi</label>
<textarea name="deskripsi" class="form-control" rows="4"
placeholder="Jelaskan kualitas produk Anda..." required></textarea>
</div> </div>
</div> </div>
{{-- UPLOAD GAMBAR --}} <div class="form-group mb-3">
<div class="col-lg-4"> <label class="fw-bold">Harga (Rp)</label>
<h5 class="mb-4">Media Produk</h5> <input type="number" name="harga" class="form-control" placeholder="0" required min="0">
</div>
{{-- FOTO UTAMA --}} <div class="form-group mb-3">
<div class="card bg-light border-0 mb-4"> <label class="fw-bold">Deskripsi</label>
<div class="card-body"> <textarea name="deskripsi" class="form-control" rows="4"
<label class="fw-bold mb-2">Foto Utama</label> placeholder="Jelaskan kualitas produk Anda..." required></textarea>
</div>
{{-- AREA PREVIEW --}} <div class="row">
<div class="mb-2 bg-white border rounded d-flex align-items-center justify-content-center position-relative overflow-hidden" <div class="col-md-6 mb-3">
style="height: 150px;"> <label class="form-label">Provinsi</label>
<select name="provinsi_code" id="provinsi" class="form-select" required>
{{-- Ikon Upload --}} <option value="">Pilih Provinsi</option>
<div id="placeholder-utama" class="text-center text-muted"> @foreach ($provinsis as $prov)
<i class="bi bi-cloud-arrow-up fs-1"></i> <option value="{{ $prov->code }}">{{ $prov->name }}</option>
<div class="small mt-1">Klik untuk upload</div> @endforeach
</div> </select>
{{-- Tampilan Gambar --}}
<img id="preview-utama" src="#"
class="img-fluid w-100 h-100 object-fit-contain d-none">
</div>
<input type="file" name="foto_produk" class="form-control form-control-sm"
accept="image/*" required onchange="previewMainImage(this)">
<small class="text-muted">Wajib. Tampil di halaman depan.</small>
</div>
</div> </div>
<div class="col-md-6 mb-3">
{{-- GALERI TAMBAHAN --}} <label class="form-label">Kota / Kabupaten</label>
<div class="card bg-light border-0"> <select name="kota_code" id="kota" class="form-select" required disabled>
<div class="card-body"> <option value="">Pilih Kota/Kabupaten</option>
<div class="d-flex justify-content-between align-items-center mb-2"> </select>
<label class="fw-bold m-0">Galeri Tambahan</label> </div>
<span class="badge bg-primary rounded-pill" id="count-badge">0/3</span> <div class="col-md-6 mb-3">
</div> <label class="form-label">Kecamatan</label>
<select name="kecamatan_code" id="kecamatan" class="form-select" required disabled>
{{-- Container Preview Grid --}} <option value="">Pilih Kecamatan</option>
<div id="gallery-preview-container" class="row g-2 mb-3"> </select>
{{-- Gambar preview --}} </div>
</div> <div class="col-md-6 mb-3">
<label class="form-label">Desa / Kelurahan</label>
{{-- Input File Visual --}} <select name="desa_code" id="desa" class="form-select" required disabled>
<div class="mb-2"> <option value="">Pilih Desa</option>
<input type="file" id="input-gallery-visual" </select>
class="form-control form-control-sm" accept="image/*" multiple>
</div>
{{-- Input File Hidden --}}
<input type="file" name="foto_tambahan[]" id="real-input-gallery" class="d-none"
multiple>
<small class="text-muted d-block" style="font-size: 0.8rem;">
<i class="bi bi-info-circle me-1"></i>Maksimal 3 foto. Bisa dihapus sebelum
upload.
</small>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="border-top pt-3 mt-4 text-end"> {{-- UPLOAD GAMBAR --}}
<a href="{{ route('petani.produk.index') }}" class="btn btn-secondary me-2">Batal</a> <div class="col-lg-4">
<button type="submit" class="btn btn-primary px-4">Simpan Produk</button> <h5 class="mb-4">Media Produk</h5>
{{-- FOTO UTAMA --}}
<div class="card bg-light border-0 mb-4">
<div class="card-body">
<label class="fw-bold mb-2">Foto Utama</label>
{{-- AREA PREVIEW --}}
<div class="mb-2 bg-white border rounded d-flex align-items-center justify-content-center position-relative overflow-hidden"
style="height: 150px;">
{{-- Ikon Upload --}}
<div id="placeholder-utama" class="text-center text-muted">
<i class="bi bi-cloud-arrow-up fs-1"></i>
<div class="small mt-1">Klik untuk upload</div>
</div>
{{-- Tampilan Gambar --}}
<img id="preview-utama" src="#"
class="img-fluid w-100 h-100 object-fit-contain d-none">
</div>
<input type="file" name="foto_produk" class="form-control form-control-sm"
accept="image/*" required onchange="previewMainImage(this)">
<small class="text-muted">Wajib. Tampil di halaman depan.</small>
</div>
</div>
{{-- GALERI TAMBAHAN --}}
<div class="card bg-light border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="fw-bold m-0">Galeri Tambahan</label>
<span class="badge bg-primary rounded-pill" id="count-badge">0/3</span>
</div>
{{-- Container Preview Grid --}}
<div id="gallery-preview-container" class="row g-2 mb-3">
{{-- Gambar preview --}}
</div>
{{-- Input File Visual --}}
<div class="mb-2">
<input type="file" id="input-gallery-visual"
class="form-control form-control-sm" accept="image/*" multiple>
</div>
{{-- Input File Hidden --}}
<input type="file" name="foto_tambahan[]" id="real-input-gallery" class="d-none"
multiple>
<small class="text-muted d-block" style="font-size: 0.8rem;">
<i class="bi bi-info-circle me-1"></i>Maksimal 3 foto. Bisa dihapus sebelum
upload.
</small>
</div>
</div>
</div> </div>
</form> </div>
</div>
<div class="border-top pt-3 mt-4 text-end">
<a href="{{ route('petani.produk.index') }}" class="btn btn-secondary me-2">Batal</a>
<button type="submit" class="btn btn-primary px-4">Simpan Produk</button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
</div>
<script> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
// ---------------------------------------------------- <script>
// PREVIEW FOTO UTAMA $(document).ready(function() {
// ---------------------------------------------------- // When Provinsi changes, fetch Kota
function previewMainImage(input) { $('#provinsi').on('change', function() {
const preview = document.getElementById('preview-utama'); let code = $(this).val();
const placeholder = document.getElementById('placeholder-utama'); $('#kota').html('<option value="">Memuat...</option>').prop('disabled', true);
$('#kecamatan').html('<option value="">== Pilih Kecamatan ==</option>').prop('disabled', true);
$('#desa').html('<option value="">== Pilih Desa ==</option>').prop('disabled', true);
if (input.files && input.files[0]) { if (code) {
var reader = new FileReader(); $.ajax({
url: "{{ route('get.kota') }}"
reader.onload = function (e) { , type: "POST"
preview.src = e.target.result; , data: {
code: code
// Tampilkan Gambar, Sembunyikan Ikon , _token: '{{ csrf_token() }}'
preview.classList.remove('d-none'); }
placeholder.classList.add('d-none'); , success: function(data) {
} $('#kota').html('<option value="">== Pilih Kota/Kabupaten ==</option>');
$.each(data, function(key, value) {
reader.readAsDataURL(input.files[0]); $('#kota').append('<option value="' + value.code + '">' + value.name + '</option>');
} else { });
preview.src = '#'; $('#kota').prop('disabled', false);
preview.classList.add('d-none'); }
placeholder.classList.remove('d-none'); });
} }
});
// When Kota changes, fetch Kecamatan
$('#kota').on('change', function() {
let code = $(this).val();
$('#kecamatan').html('<option value="">Memuat...</option>').prop('disabled', true);
$('#desa').html('<option value="">== Pilih Desa ==</option>').prop('disabled', true);
if (code) {
$.ajax({
url: "{{ route('get.kecamatan') }}"
, type: "POST"
, data: {
code: code
, _token: '{{ csrf_token() }}'
}
, success: function(data) {
$('#kecamatan').html('<option value="">== Pilih Kecamatan ==</option>');
$.each(data, function(key, value) {
$('#kecamatan').append('<option value="' + value.code + '">' + value.name + '</option>');
});
$('#kecamatan').prop('disabled', false);
}
});
}
});
// When Kecamatan changes, fetch Desa
$('#kecamatan').on('change', function() {
let code = $(this).val();
$('#desa').html('<option value="">Memuat...</option>').prop('disabled', true);
if (code) {
$.ajax({
url: "{{ route('get.desa') }}"
, type: "POST"
, data: {
code: code
, _token: '{{ csrf_token() }}'
}
, success: function(data) {
$('#desa').html('<option value="">== Pilih Desa ==</option>');
$.each(data, function(key, value) {
$('#desa').append('<option value="' + value.code + '">' + value.name + '</option>');
});
$('#desa').prop('disabled', false);
}
});
}
});
});
// ----------------------------------------------------
// PREVIEW FOTO UTAMA
// ----------------------------------------------------
function previewMainImage(input) {
const preview = document.getElementById('preview-utama');
const placeholder = document.getElementById('placeholder-utama');
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
// Tampilkan Gambar, Sembunyikan Ikon
preview.classList.remove('d-none');
placeholder.classList.add('d-none');
}
reader.readAsDataURL(input.files[0]);
} else {
preview.src = '#';
preview.classList.add('d-none');
placeholder.classList.remove('d-none');
}
}
// ----------------------------------------------------
// LOGIC GALERI TAMBAHAN (KERANJANG UPLOAD)
// ----------------------------------------------------
const inputVisual = document.getElementById('input-gallery-visual');
const inputReal = document.getElementById('real-input-gallery');
const container = document.getElementById('gallery-preview-container');
const countBadge = document.getElementById('count-badge');
// "Keranjang" virtual untuk menampung file
let dt = new DataTransfer();
inputVisual.addEventListener('change', function() {
const newFiles = this.files;
// Cek Limit Total
if (dt.items.length + newFiles.length > 3) {
alert('Maksimal hanya boleh 3 gambar tambahan!');
this.value = '';
return;
} }
// ---------------------------------------------------- // Loop file baru
// LOGIC GALERI TAMBAHAN (KERANJANG UPLOAD) for (let i = 0; i < newFiles.length; i++) {
// ---------------------------------------------------- const file = newFiles[i];
const inputVisual = document.getElementById('input-gallery-visual');
const inputReal = document.getElementById('real-input-gallery');
const container = document.getElementById('gallery-preview-container');
const countBadge = document.getElementById('count-badge');
// "Keranjang" virtual untuk menampung file dt.items.add(file);
let dt = new DataTransfer();
inputVisual.addEventListener('change', function () { // Buat HTML Preview
const newFiles = this.files; const reader = new FileReader();
reader.onload = function(e) {
const col = document.createElement('div');
col.className = 'col-4 position-relative new-image-preview';
// Cek Limit Total col.innerHTML = `
if (dt.items.length + newFiles.length > 3) {
alert('Maksimal hanya boleh 3 gambar tambahan!');
this.value = '';
return;
}
// Loop file baru
for (let i = 0; i < newFiles.length; i++) {
const file = newFiles[i];
dt.items.add(file);
// Buat HTML Preview
const reader = new FileReader();
reader.onload = function (e) {
const col = document.createElement('div');
col.className = 'col-4 position-relative new-image-preview';
col.innerHTML = `
<img src="${e.target.result}" class="rounded w-100 border bg-white" style="height: 70px; object-fit: cover;"> <img src="${e.target.result}" class="rounded w-100 border bg-white" style="height: 70px; object-fit: cover;">
<span class="position-absolute top-0 end-0 badge bg-secondary text-white rounded-circle m-1 shadow-sm remove-btn" <span class="position-absolute top-0 end-0 badge bg-secondary text-white rounded-circle m-1 shadow-sm remove-btn"
style="cursor: pointer;">&times;</span> style="cursor: pointer;">&times;</span>
`; `;
// Event Listener Tombol Hapus (X) // Event Listener Tombol Hapus (X)
col.querySelector('.remove-btn').addEventListener('click', function () { col.querySelector('.remove-btn').addEventListener('click', function() {
// Hapus file dari keranjang berdasarkan nama & size // Hapus file dari keranjang berdasarkan nama & size
for (let j = 0; j < dt.items.length; j++) { for (let j = 0; j < dt.items.length; j++) {
if (dt.files[j].name === file.name && dt.files[j].size === file.size) { if (dt.files[j].name === file.name && dt.files[j].size === file.size) {
dt.items.remove(j); dt.items.remove(j);
break; break;
}
} }
inputReal.files = dt.files; }
col.remove(); inputReal.files = dt.files;
updateBadge(); col.remove();
}); updateBadge();
});
container.appendChild(col); container.appendChild(col);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}
inputReal.files = dt.files;
this.value = '';
updateBadge();
});
function updateBadge() {
let total = dt.items.length;
countBadge.innerText = total + '/3';
// Visual Feedback jika penuh
if (total >= 3) {
inputVisual.disabled = true;
inputVisual.setAttribute('title', 'Slot penuh (Maks 3)');
countBadge.className = 'badge bg-danger rounded-pill';
} else {
inputVisual.disabled = false;
inputVisual.removeAttribute('title');
countBadge.className = 'badge bg-primary rounded-pill';
}
} }
</script>
inputReal.files = dt.files;
this.value = '';
updateBadge();
});
function updateBadge() {
let total = dt.items.length;
countBadge.innerText = total + '/3';
// Visual Feedback jika penuh
if (total >= 3) {
inputVisual.disabled = true;
inputVisual.setAttribute('title', 'Slot penuh (Maks 3)');
countBadge.className = 'badge bg-danger rounded-pill';
} else {
inputVisual.disabled = false;
inputVisual.removeAttribute('title');
countBadge.className = 'badge bg-primary rounded-pill';
}
}
</script>
@endsection @endsection

View File

@ -4,263 +4,363 @@
@section('page-title', 'Edit Produk') @section('page-title', 'Edit Produk')
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
{{-- Form Utama --}} {{-- Form Utama --}}
<form action="{{ route('petani.produk.update', $produk->id) }}" method="POST" <form action="{{ route('petani.produk.update', $produk->id) }}" method="POST" enctype="multipart/form-data">
enctype="multipart/form-data"> @csrf
@csrf @method('PUT')
@method('PUT')
<div class="row"> <div class="row">
{{-- DATA PRODUK --}} {{-- DATA PRODUK --}}
<div class="col-lg-8"> <div class="col-lg-8">
<h5 class="mb-4">Informasi Produk</h5> <h5 class="mb-4">Informasi Produk</h5>
<div class="form-group mb-3"> <div class="form-group mb-3">
<label class="fw-bold">Nama Produk</label> <label class="fw-bold">Nama Produk</label>
<input type="text" name="nama_produk" class="form-control" <input type="text" name="nama_produk" class="form-control" value="{{ $produk->nama_produk }}" required>
value="{{ $produk->nama_produk }}" required> </div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="fw-bold">Kategori</label>
<select name="kategori_id" class="form-select" required>
<option value="" disabled>Pilih Kategori</option>
@foreach($kategoris as $kat)
<option value="{{ $kat->id }}" {{ (old('kategori_id', $produk->kategori_id) ==
$kat->id) ? 'selected' : '' }}>
{{ $kat->nama_kategori }}
</option>
@endforeach
</select>
</div> </div>
<div class="col-md-6 mb-3">
<div class="row"> <label class="fw-bold">Stok</label>
<div class="col-md-6 mb-3"> <input type="number" name="stok" class="form-control" value="{{ $produk->stok }}" required>
<label class="fw-bold">Kategori</label>
<select name="kategori_id" class="form-select" required>
<option value="" disabled>Pilih Kategori</option>
@foreach($kategoris as $kat)
<option value="{{ $kat->id }}" {{ (old('kategori_id', $produk->kategori_id) == $kat->id) ? 'selected' : '' }}>
{{ $kat->nama_kategori }}
</option>
@endforeach
</select>
</div>
<div class="col-md-6 mb-3">
<label class="fw-bold">Stok</label>
<input type="number" name="stok" class="form-control" value="{{ $produk->stok }}"
required>
</div>
</div>
<div class="form-group mb-3">
<label class="fw-bold">Harga (Rp)</label>
<input type="number" name="harga" class="form-control" value="{{ $produk->harga }}"
required>
</div>
<div class="form-group mb-3">
<label class="fw-bold">Deskripsi</label>
<textarea name="deskripsi" class="form-control" rows="4"
required>{{ $produk->deskripsi }}</textarea>
</div> </div>
</div> </div>
{{-- KELOLA GAMBAR --}} <div class="form-group mb-3">
<div class="col-lg-4"> <label class="fw-bold">Harga (Rp)</label>
<h5 class="mb-4">Kelola Gambar</h5> <input type="number" name="harga" class="form-control" value="{{ $produk->harga }}" required>
</div>
{{-- FOTO UTAMA --}} <div class="form-group mb-3">
<div class="card bg-light border-0 mb-4"> <label class="fw-bold">Deskripsi</label>
<div class="card-body"> <textarea name="deskripsi" class="form-control" rows="4" required>{{ $produk->deskripsi }}</textarea>
<label class="fw-bold mb-2">Foto Utama</label> </div>
{{-- AREA PREVIEW --}} <div class="row">
<div class="mb-2 bg-white border rounded d-flex align-items-center justify-content-center position-relative overflow-hidden" <div class="col-md-6 mb-3">
style="height: 150px;"> <label class="form-label">Provinsi</label>
<select name="provinsi_code" id="provinsi" class="form-select" required>
<div id="placeholder-utama" <option value="">Pilih Provinsi</option>
class="text-center text-muted {{ $produk->foto_produk ? 'd-none' : '' }}"> @foreach ($provinsis as $prov)
<i class="bi bi-cloud-arrow-up fs-1"></i> <option value="{{ $prov->code }}" {{ $produk->provinsi_code == $prov->code ?
<div class="small mt-1">Upload foto baru</div> 'selected' : '' }}>{{ $prov->name }}</option>
</div> @endforeach
</select>
<img id="preview-utama"
src="{{ $produk->foto_produk ? asset('storage/' . $produk->foto_produk) : '#' }}"
class="img-fluid w-100 h-100 object-fit-contain {{ $produk->foto_produk ? '' : 'd-none' }}">
</div>
{{-- Input File --}}
<input type="file" name="foto_produk" class="form-control form-control-sm"
accept="image/*" onchange="previewMainImage(this)">
<small class="text-muted">Biarkan kosong jika tidak ingin mengganti foto.</small>
</div>
</div> </div>
<div class="col-md-6 mb-3">
{{-- GALERI TAMBAHAN --}} <label class="form-label">Kota / Kabupaten</label>
<div class="card bg-light border-0"> <select name="kota_code" id="kota" class="form-select" required>
<div class="card-body"> <option value="">Pilih Kota/Kabupaten</option>
<div class="d-flex justify-content-between align-items-center mb-2"> @foreach ($kotas as $kota)
<label class="fw-bold m-0">Galeri Tambahan</label> <option value="{{ $kota->code }}" {{ $produk->kota_code == $kota->code ?
<span class="badge bg-primary rounded-pill" id="count-badge">0/3</span> 'selected' : '' }}>{{ $kota->name }}</option>
</div> @endforeach
</select>
{{-- CONTAINER PREVIEW --}} </div>
<div id="gallery-preview-container" class="row g-2 mb-3"> <div class="col-md-6 mb-3">
<label class="form-label">Kecamatan</label>
{{-- Gambar Lama dari Database --}} <select name="kecamatan_code" id="kecamatan" class="form-select" required>
@foreach($produk->images as $img) <option value="">Pilih Kecamatan</option>
<div class="col-4 position-relative existing-image-wrapper" @foreach ($kecamatans as $kec)
id="existing-img-{{ $img->id }}"> <option value="{{ $kec->code }}" {{ $produk->kecamatan_code == $kec->code ?
<img src="{{ asset('storage/' . $img->foto) }}" 'selected' : '' }}>{{ $kec->name }}</option>
class="rounded w-100 border bg-white" @endforeach
style="height: 70px; object-fit: cover;"> </select>
</div>
{{-- Tombol Hapus Gambar Lama --}} <div class="col-md-6 mb-3">
<a href="javascript:void(0)" <label class="form-label">Desa / Kelurahan</label>
class="position-absolute top-0 end-0 badge bg-danger text-white rounded-circle text-decoration-none m-1 shadow-sm" <select name="desa_code" id="desa" class="form-select" required>
onclick="if(confirm('Hapus foto ini?')) deleteExistingImage({{ $img->id }});" <option value="">Pilih Desa</option>
style="cursor: pointer;">&times;</a> @foreach ($desas as $desa)
</div> <option value="{{ $desa->code }}" {{ $produk->desa_code == $desa->code ?
@endforeach 'selected' : '' }}>{{ $desa->name }}</option>
@endforeach
</div> </select>
{{-- INPUT FILE --}}
<div class="mb-3">
<input type="file" id="input-gallery-visual"
class="form-control form-control-sm" accept="image/*" multiple>
</div>
{{-- INPUT FILE HIDDEN --}}
<input type="file" name="foto_tambahan[]" id="real-input-gallery" class="d-none"
multiple>
<small class="text-muted d-block" style="font-size: 0.8rem;">
<i class="bi bi-info-circle me-1"></i>Maksimal 3 foto tambahan total.
</small>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="border-top pt-3 mt-4 text-end"> {{-- KELOLA GAMBAR --}}
<a href="{{ route('petani.produk.index') }}" class="btn btn-secondary me-2">Batal</a> <div class="col-lg-4">
<button type="submit" class="btn btn-primary px-4">Update Produk</button> <h5 class="mb-4">Kelola Gambar</h5>
</div>
</form>
{{-- FORM TERSEMBUNYI UNTUK HAPUS GAMBAR LAMA --}} {{-- FOTO UTAMA --}}
@foreach($produk->images as $img) <div class="card bg-light border-0 mb-4">
<form id="form-del-img-{{ $img->id }}" action="{{ route('petani.produk.image.delete', $img->id) }}" <div class="card-body">
method="POST" style="display: none;"> <label class="fw-bold mb-2">Foto Utama</label>
@csrf @method('DELETE')
</form> {{-- AREA PREVIEW --}}
@endforeach <div class="mb-2 bg-white border rounded d-flex align-items-center justify-content-center position-relative overflow-hidden" style="height: 150px;">
</div>
<div id="placeholder-utama" class="text-center text-muted {{ $produk->foto_produk ? 'd-none' : '' }}">
<i class="bi bi-cloud-arrow-up fs-1"></i>
<div class="small mt-1">Upload foto baru</div>
</div>
<img id="preview-utama" src="{{ $produk->foto_produk ? asset('storage/' . $produk->foto_produk) : '#' }}" class="img-fluid w-100 h-100 object-fit-contain {{ $produk->foto_produk ? '' : 'd-none' }}">
</div>
{{-- Input File --}}
<input type="file" name="foto_produk" class="form-control form-control-sm" accept="image/*" onchange="previewMainImage(this)">
<small class="text-muted">Biarkan kosong jika tidak ingin mengganti foto.</small>
</div>
</div>
{{-- GALERI TAMBAHAN --}}
<div class="card bg-light border-0">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="fw-bold m-0">Galeri Tambahan</label>
<span class="badge bg-primary rounded-pill" id="count-badge">0/3</span>
</div>
{{-- CONTAINER PREVIEW --}}
<div id="gallery-preview-container" class="row g-2 mb-3">
{{-- Gambar Lama dari Database --}}
@foreach($produk->images as $img)
<div class="col-4 position-relative existing-image-wrapper" id="existing-img-{{ $img->id }}">
<img src="{{ asset('storage/' . $img->foto) }}" class="rounded w-100 border bg-white" style="height: 70px; object-fit: cover;">
{{-- Tombol Hapus Gambar Lama --}}
<a href="javascript:void(0)" class="position-absolute top-0 end-0 badge bg-danger text-white rounded-circle text-decoration-none m-1 shadow-sm" onclick="if(confirm('Hapus foto ini?')) deleteExistingImage({{ $img->id }});" style="cursor: pointer;">&times;</a>
</div>
@endforeach
</div>
{{-- INPUT FILE --}}
<div class="mb-3">
<input type="file" id="input-gallery-visual" class="form-control form-control-sm" accept="image/*" multiple>
</div>
{{-- INPUT FILE HIDDEN --}}
<input type="file" name="foto_tambahan[]" id="real-input-gallery" class="d-none" multiple>
<small class="text-muted d-block" style="font-size: 0.8rem;">
<i class="bi bi-info-circle me-1"></i>Maksimal 3 foto tambahan total.
</small>
</div>
</div>
</div>
</div>
<div class="border-top pt-3 mt-4 text-end">
<a href="{{ route('petani.produk.index') }}" class="btn btn-secondary me-2">Batal</a>
<button type="submit" class="btn btn-primary px-4">Update Produk</button>
</div>
</form>
{{-- FORM TERSEMBUNYI UNTUK HAPUS GAMBAR LAMA --}}
@foreach($produk->images as $img)
<form id="form-del-img-{{ $img->id }}" action="{{ route('petani.produk.image.delete', $img->id) }}" method="POST" style="display: none;">
@csrf @method('DELETE')
</form>
@endforeach
</div> </div>
</div> </div>
</div> </div>
</div>
<script> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
// ---------------------------------------------------- <script>
// PREVIEW FOTO UTAMA $(document).ready(function() {
// ---------------------------------------------------- $('#provinsi').on('change', function() {
function previewMainImage(input) { let code = $(this).val();
$('#kota').html('<option value="">Memuat...</option>').prop('disabled', true);
$('#kecamatan').html('<option value="">== Pilih Kecamatan ==</option>').prop('disabled', true);
$('#desa').html('<option value="">== Pilih Desa ==</option>').prop('disabled', true);
if (code) {
$.ajax({
url: "{{ route('get.kota') }}"
, type: "POST"
, data: {
code: code
, _token: '{{ csrf_token() }}'
}
, success: function(data) {
$('#kota').html('<option value="">== Pilih Kota/Kabupaten ==</option>');
$.each(data, function(key, value) {
$('#kota').append('<option value="' + value.code + '">' + value.name + '</option>');
});
$('#kota').prop('disabled', false);
}
});
}
});
$('#kota').on('change', function() {
let code = $(this).val();
$('#kecamatan').html('<option value="">Memuat...</option>').prop('disabled', true);
$('#desa').html('<option value="">== Pilih Desa ==</option>').prop('disabled', true);
if (code) {
$.ajax({
url: "{{ route('get.kecamatan') }}"
, type: "POST"
, data: {
code: code
, _token: '{{ csrf_token() }}'
}
, success: function(data) {
$('#kecamatan').html('<option value="">== Pilih Kecamatan ==</option>');
$.each(data, function(key, value) {
$('#kecamatan').append('<option value="' + value.code + '">' + value.name + '</option>');
});
$('#kecamatan').prop('disabled', false);
}
});
}
});
$('#kecamatan').on('change', function() {
let code = $(this).val();
$('#desa').html('<option value="">Memuat...</option>').prop('disabled', true);
if (code) {
$.ajax({
url: "{{ route('get.desa') }}"
, type: "POST"
, data: {
code: code
, _token: '{{ csrf_token() }}'
}
, success: function(data) {
$('#desa').html('<option value="">== Pilih Desa ==</option>');
$.each(data, function(key, value) {
$('#desa').append('<option value="' + value.code + '">' + value.name + '</option>');
});
$('#desa').prop('disabled', false);
}
});
}
});
});
// ----------------------------------------------------
// PREVIEW FOTO UTAMA
// ----------------------------------------------------
function previewMainImage(input) {
const preview = document.getElementById('preview-utama'); const preview = document.getElementById('preview-utama');
const placeholder = document.getElementById('placeholder-utama'); const placeholder = document.getElementById('placeholder-utama');
if (input.files && input.files[0]) { if (input.files && input.files[0]) {
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
preview.src = e.target.result; preview.src = e.target.result;
preview.classList.remove('d-none'); preview.classList.remove('d-none');
placeholder.classList.add('d-none'); placeholder.classList.add('d-none');
} }
reader.readAsDataURL(input.files[0]); reader.readAsDataURL(input.files[0]);
} }
} }
// ---------------------------------------------------- // ----------------------------------------------------
// LOGIC HAPUS GAMBAR LAMA (DATABASE) // LOGIC HAPUS GAMBAR LAMA (DATABASE)
// ---------------------------------------------------- // ----------------------------------------------------
function deleteExistingImage(id) { function deleteExistingImage(id) {
document.getElementById('form-del-img-' + id).submit(); document.getElementById('form-del-img-' + id).submit();
}
// ----------------------------------------------------
// LOGIC PREVIEW + ACCUMULATE
// ----------------------------------------------------
const inputVisual = document.getElementById('input-gallery-visual');
const inputReal = document.getElementById('real-input-gallery');
const container = document.getElementById('gallery-preview-container');
const countBadge = document.getElementById('count-badge');
let dt = new DataTransfer();
let existingCount = document.querySelectorAll('.existing-image-wrapper').length;
updateBadge();
inputVisual.addEventListener('change', function() {
const newFiles = this.files;
if (existingCount + dt.items.length + newFiles.length > 3) {
alert('Maksimal total hanya boleh 3 gambar tambahan!');
this.value = '';
return;
} }
// ---------------------------------------------------- // Loop file yang baru dipilih
// LOGIC PREVIEW + ACCUMULATE for (let i = 0; i < newFiles.length; i++) {
// ---------------------------------------------------- const file = newFiles[i];
const inputVisual = document.getElementById('input-gallery-visual');
const inputReal = document.getElementById('real-input-gallery');
const container = document.getElementById('gallery-preview-container');
const countBadge = document.getElementById('count-badge');
let dt = new DataTransfer(); // Tambahkan ke keranjang DataTransfer
let existingCount = document.querySelectorAll('.existing-image-wrapper').length; dt.items.add(file);
updateBadge();
inputVisual.addEventListener('change', function () { // Buat Preview Element
const newFiles = this.files; const reader = new FileReader();
reader.onload = function(e) {
const col = document.createElement('div');
col.className = 'col-4 position-relative new-image-preview';
if (existingCount + dt.items.length + newFiles.length > 3) { // HTML Preview
alert('Maksimal total hanya boleh 3 gambar tambahan!'); col.innerHTML = `
this.value = '';
return;
}
// Loop file yang baru dipilih
for (let i = 0; i < newFiles.length; i++) {
const file = newFiles[i];
// Tambahkan ke keranjang DataTransfer
dt.items.add(file);
// Buat Preview Element
const reader = new FileReader();
reader.onload = function (e) {
const col = document.createElement('div');
col.className = 'col-4 position-relative new-image-preview';
// HTML Preview
col.innerHTML = `
<img src="${e.target.result}" class="rounded w-100 border bg-white" style="height: 70px; object-fit: cover;"> <img src="${e.target.result}" class="rounded w-100 border bg-white" style="height: 70px; object-fit: cover;">
<span class="position-absolute top-0 end-0 badge bg-secondary text-white rounded-circle m-1 shadow-sm remove-btn" <span class="position-absolute top-0 end-0 badge bg-secondary text-white rounded-circle m-1 shadow-sm remove-btn"
style="cursor: pointer;">&times;</span> style="cursor: pointer;">&times;</span>
`; `;
// Event Hapus Preview // Event Hapus Preview
col.querySelector('.remove-btn').addEventListener('click', function () { col.querySelector('.remove-btn').addEventListener('click', function() {
for (let j = 0; j < dt.items.length; j++) { for (let j = 0; j < dt.items.length; j++) {
if (dt.files[j].name === file.name && dt.files[j].size === file.size) { if (dt.files[j].name === file.name && dt.files[j].size === file.size) {
dt.items.remove(j); dt.items.remove(j);
break; break;
}
} }
// Sinkronisasi ke Input Real }
inputReal.files = dt.files; // Sinkronisasi ke Input Real
col.remove(); inputReal.files = dt.files;
updateBadge(); col.remove();
}); updateBadge();
});
container.appendChild(col); container.appendChild(col);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}
inputReal.files = dt.files;
this.value = '';
updateBadge();
});
function updateBadge() {
let total = existingCount + dt.items.length;
countBadge.innerText = total + '/3';
if (total >= 3) {
inputVisual.disabled = true;
countBadge.classList.remove('bg-primary');
countBadge.classList.add('bg-danger');
inputVisual.setAttribute('title', 'Kapasitas penuh');
} else {
inputVisual.disabled = false;
countBadge.classList.remove('bg-danger');
countBadge.classList.add('bg-primary');
inputVisual.removeAttribute('title');
}
} }
</script>
@endsection inputReal.files = dt.files;
this.value = '';
updateBadge();
});
function updateBadge() {
let total = existingCount + dt.items.length;
countBadge.innerText = total + '/3';
if (total >= 3) {
inputVisual.disabled = true;
countBadge.classList.remove('bg-primary');
countBadge.classList.add('bg-danger');
inputVisual.setAttribute('title', 'Kapasitas penuh');
} else {
inputVisual.disabled = false;
countBadge.classList.remove('bg-danger');
countBadge.classList.add('bg-primary');
inputVisual.removeAttribute('title');
}
}
</script>
@endsection

View File

@ -123,6 +123,11 @@
Route::put('/petani/profile', [ProfileController::class, 'updatePetani'])->name('petani.profile.update'); Route::put('/petani/profile', [ProfileController::class, 'updatePetani'])->name('petani.profile.update');
}); });
// Route untuk Dropdown Wilayah Laravolt
Route::post('/get-kota', [\App\Http\Controllers\WilayahController::class, 'getKota'])->name('get.kota');
Route::post('/get-kecamatan', [\App\Http\Controllers\WilayahController::class, 'getKecamatan'])->name('get.kecamatan');
Route::post('/get-desa', [\App\Http\Controllers\WilayahController::class, 'getDesa'])->name('get.desa');
// --- CEK NIK GAPOKTAN AJAX --- // --- CEK NIK GAPOKTAN AJAX ---
Route::post('/cek-nik-gapoktan', function (Request $request) { Route::post('/cek-nik-gapoktan', function (Request $request) {