feat: tambahkan grafik produk terlaris & kestabilan harga di dashboard admin, hapus fitur verifikasi petani
This commit is contained in:
parent
f01c00fa87
commit
6097b439fc
|
|
@ -7,75 +7,63 @@
|
|||
use App\Models\Petani;
|
||||
use App\Models\Produk;
|
||||
use App\Models\Transaksi;
|
||||
use App\Models\DetailTransaksi;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function dashboard()
|
||||
{
|
||||
// STATISTIK
|
||||
$totalPetani = Petani::where('status_akun', 'aktif')->count();
|
||||
$petaniPending = Petani::where('status_akun', 'menunggu')->count();
|
||||
$totalProduk = Produk::count();
|
||||
$totalTransaksi = Transaksi::count();
|
||||
$transaksiTerbaru = Transaksi::with(['pembeli', 'petani'])->latest()->take(5)->get();
|
||||
|
||||
$transaksiTerbaru = Transaksi::with(['pembeli', 'petani'])
|
||||
->latest()
|
||||
// PRODUK TERLARIS
|
||||
$produkTerlaris = Produk::withSum(['detailTransaksis as total_terjual' => function ($query) {
|
||||
$query->whereHas('transaksi', function ($q) {
|
||||
$q->where('status', '!=', 'batal');
|
||||
});
|
||||
}], 'jumlah')
|
||||
->orderByDesc('total_terjual')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
$labelTerlaris = $produkTerlaris->pluck('nama_produk');
|
||||
$dataTerlaris = $produkTerlaris->pluck('total_terjual');
|
||||
|
||||
// Menghitung rata-rata harga jual harian selama 7 hari terakhir
|
||||
$hargaStabil = DetailTransaksi::join('transaksis', 'detail_transaksis.transaksi_id', '=', 'transaksis.id')
|
||||
->selectRaw('DATE(transaksis.tanggal_transaksi) as tanggal, AVG(detail_transaksis.harga_satuan) as rata_harga')
|
||||
->where('transaksis.status', '!=', 'batal')
|
||||
->groupBy('tanggal')
|
||||
->orderBy('tanggal', 'asc')
|
||||
->take(7)
|
||||
->get();
|
||||
|
||||
$labelHarga = $hargaStabil->pluck('tanggal')->map(function($date) {
|
||||
return \Carbon\Carbon::parse($date)->format('d M');
|
||||
});
|
||||
$dataHarga = $hargaStabil->pluck('rata_harga');
|
||||
|
||||
return view('admin.dashboard', compact(
|
||||
'totalPetani',
|
||||
'petaniPending',
|
||||
'totalProduk',
|
||||
'totalTransaksi',
|
||||
'transaksiTerbaru'
|
||||
'totalPetani', 'totalProduk', 'totalTransaksi', 'transaksiTerbaru',
|
||||
'labelTerlaris', 'dataTerlaris', 'labelHarga', 'dataHarga'
|
||||
));
|
||||
}
|
||||
|
||||
public function monitoring()
|
||||
{
|
||||
$produks = Produk::with('petani')->latest()->paginate(10);
|
||||
$transaksis = Transaksi::with(['pembeli', 'petani'])
|
||||
->latest()
|
||||
->paginate(10);
|
||||
|
||||
$transaksis = Transaksi::with(['pembeli', 'petani'])->latest()->paginate(10);
|
||||
return view('admin.monitoring', compact('produks', 'transaksis'));
|
||||
}
|
||||
|
||||
public function transaksiDetail($id)
|
||||
{
|
||||
$transaksi = Transaksi::with(['pembeli', 'petani', 'detailTransaksis.produk'])
|
||||
->findOrFail($id);
|
||||
|
||||
$transaksi = Transaksi::with(['pembeli', 'petani', 'detailTransaksis.produk'])->findOrFail($id);
|
||||
return view('admin.transaksi_detail', compact('transaksi'));
|
||||
}
|
||||
|
||||
public function verifikasiIndex()
|
||||
{
|
||||
$petanis = Petani::orderBy('created_at', 'desc')->get();
|
||||
return view('admin.verifikasi.index', compact('petanis'));
|
||||
}
|
||||
|
||||
public function verifikasiShow($id)
|
||||
{
|
||||
$petani = Petani::findOrFail($id);
|
||||
return view('admin.verifikasi.show', compact('petani'));
|
||||
}
|
||||
|
||||
public function verifikasiApprove($id)
|
||||
{
|
||||
$petani = Petani::findOrFail($id);
|
||||
$petani->status_akun = 'aktif';
|
||||
$petani->save();
|
||||
|
||||
return redirect('admin/verifikasi')->with('success', 'Pendaftaran Petani BERHASIL diterima.');
|
||||
}
|
||||
|
||||
public function verifikasiReject($id)
|
||||
{
|
||||
$petani = Petani::findOrFail($id);
|
||||
$petani->status_akun = 'ditolak';
|
||||
$petani->save();
|
||||
|
||||
return redirect('admin/verifikasi')->with('success', 'Pendaftaran Petani DITOLAK.');
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ public function checkoutPage(Request $request)
|
|||
// Mendefinisikan ID Pembeli agar tidak error 'Undefined Variable'
|
||||
$pembeli_id = Auth::guard('pembeli')->id();
|
||||
|
||||
// 1. LOGIKA BELI LANGSUNG (Buy Now)
|
||||
// LOGIKA BELI LANGSUNG (Buy Now)
|
||||
if ($request->has('produk_id')) {
|
||||
$produk = Produk::with('petani')->findOrFail($request->produk_id);
|
||||
$items = collect([
|
||||
|
|
@ -41,7 +41,7 @@ public function checkoutPage(Request $request)
|
|||
return view('landing.checkout', compact('items', 'total_belanja'));
|
||||
}
|
||||
|
||||
// 2. LOGIKA CHECKOUT DARI KERANJANG (Database)
|
||||
// LOGIKA CHECKOUT DARI KERANJANG (Database)
|
||||
$cartIds = $request->query('cart_ids');
|
||||
|
||||
if (!$cartIds) {
|
||||
|
|
@ -50,7 +50,6 @@ public function checkoutPage(Request $request)
|
|||
|
||||
$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)
|
||||
|
|
@ -132,7 +131,6 @@ public function prosesCheckout(Request $request)
|
|||
->where('pembeli_id', $pembeli_id)
|
||||
->get();
|
||||
|
||||
// Kelompokkan produk berdasarkan Petani ID agar Invoice terpisah per toko
|
||||
$groupedByPetani = [];
|
||||
foreach ($cartItems as $item) {
|
||||
$groupedByPetani[$item->produk->petani_id][] = $item;
|
||||
|
|
@ -144,7 +142,7 @@ public function prosesCheckout(Request $request)
|
|||
'petani_id' => $petani_id,
|
||||
'tanggal_transaksi' => now(),
|
||||
'alamat_pengiriman' => $request->alamat_pengiriman,
|
||||
'total_harga' => 0, // Diupdate setelah menghitung subtotal
|
||||
'total_harga' => 0,
|
||||
'status' => 'menunggu konfirmasi',
|
||||
'kode_invoice' => 'INV/' . date('Ymd') . '/' . rand(1000, 9999),
|
||||
]);
|
||||
|
|
@ -169,7 +167,6 @@ public function prosesCheckout(Request $request)
|
|||
$transaksi->update(['total_harga' => $subtotal_transaksi]);
|
||||
}
|
||||
|
||||
// OTOMATIS BERSIHKAN ITEM KERANJANG YANG SUDAH DIBELI
|
||||
Cart::whereIn('id', $cartIds)->delete();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,45 +6,27 @@
|
|||
@section('content')
|
||||
<section class="row">
|
||||
<div class="col-12">
|
||||
|
||||
{{-- STATISTIK ATAS --}}
|
||||
<div class="row">
|
||||
{{-- Statistik Petani Aktif --}}
|
||||
<div class="col-6 col-lg-3 col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body px-3 py-4-5">
|
||||
<div class="col-6 col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-4-5">
|
||||
<div class="row">
|
||||
<div class="col-md-4 d-flex justify-content-center align-items-center">
|
||||
<div class="stats-icon purple"><i class="bi bi-people-fill"></i></div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h6 class="text-muted font-semibold">Petani Aktif</h6>
|
||||
<h6 class="text-muted font-semibold">Total Petani</h6>
|
||||
<h6 class="font-extrabold mb-0">{{ $totalPetani }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Statistik Menunggu Verifikasi --}}
|
||||
<div class="col-6 col-lg-3 col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body px-3 py-4-5">
|
||||
<div class="row">
|
||||
<div class="col-md-4 d-flex justify-content-center align-items-center">
|
||||
<div class="stats-icon red"><i class="bi bi-person-plus-fill"></i></div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h6 class="text-muted font-semibold">Verifikasi Masuk</h6>
|
||||
<h6 class="font-extrabold mb-0">{{ $petaniPending }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Statistik Total Produk --}}
|
||||
<div class="col-6 col-lg-3 col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body px-3 py-4-5">
|
||||
<div class="col-6 col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-4-5">
|
||||
<div class="row">
|
||||
<div class="col-md-4 d-flex justify-content-center align-items-center">
|
||||
<div class="stats-icon green"><i class="bi bi-basket-fill"></i></div>
|
||||
|
|
@ -57,11 +39,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Statistik Total Transaksi --}}
|
||||
<div class="col-6 col-lg-3 col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body px-3 py-4-5">
|
||||
<div class="col-12 col-lg-4 col-md-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body px-4 py-4-5">
|
||||
<div class="row">
|
||||
<div class="col-md-4 d-flex justify-content-center align-items-center">
|
||||
<div class="stats-icon blue"><i class="bi bi-receipt"></i></div>
|
||||
|
|
@ -76,14 +56,41 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tabel Ringkasan Transaksi --}}
|
||||
{{-- AREA GRAFIK BERSEBELAHAN --}}
|
||||
<div class="row mb-4">
|
||||
{{-- Kiri: Grafik Produk Terlaris --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="card-title mb-0"><i class="bi bi-bar-chart-fill text-primary me-2"></i>Produk Terlaris</h5>
|
||||
</div>
|
||||
<div class="card-body mt-3">
|
||||
<canvas id="chartTerlaris" style="max-height: 300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Kanan: Grafik Kestabilan Harga --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="card-title mb-0"><i class="bi bi-graph-up text-success me-2"></i>Kestabilan Harga Jual Petani</h5>
|
||||
</div>
|
||||
<div class="card-body mt-3">
|
||||
<canvas id="chartHarga" style="max-height: 300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- TABEL TRANSAKSI TERBARU (SAMA SEPERTI SEBELUMNYA) --}}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4>Transaksi Terbaru di Platform</h4>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0">Transaksi Terbaru di Platform</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body mt-3">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
|
|
@ -102,20 +109,12 @@
|
|||
<td class="fw-bold text-primary">#{{ $trx->kode_invoice }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-bold text-dark">
|
||||
{{ $trx->petani->nama_lengkap ?? 'Petani Tidak Ditemukan' }}
|
||||
</span>
|
||||
<small class="text-muted" style="font-size: 0.85em;">
|
||||
<i class="bi bi-shop me-1"></i>
|
||||
{{ $trx->petani->nama_usaha ?? '-' }}
|
||||
</small>
|
||||
<span class="fw-bold text-dark">{{ $trx->petani->nama_lengkap ?? 'Petani Tidak Ditemukan' }}</span>
|
||||
<small class="text-muted"><i class="bi bi-shop me-1"></i>{{ $trx->petani->nama_usaha ?? '-' }}</small>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>{{ $trx->pembeli->nama_lengkap ?? 'Guest' }}</td>
|
||||
|
||||
<td class="fw-bold">Rp {{ number_format($trx->total_harga, 0, ',', '.') }}</td>
|
||||
|
||||
<td>
|
||||
@php
|
||||
$badgeClass = match ($trx->status) {
|
||||
|
|
@ -127,30 +126,18 @@
|
|||
default => 'bg-secondary',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge {{ $badgeClass }} rounded-pill px-3">
|
||||
{{ ucwords($trx->status) }}
|
||||
</span>
|
||||
<span class="badge {{ $badgeClass }} rounded-pill px-3">{{ ucwords($trx->status) }}</span>
|
||||
</td>
|
||||
|
||||
<td class="text-muted small">{{ $trx->created_at->format('d M Y') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4 text-muted">
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<i class="bi bi-inbox fs-1 mb-2"></i>
|
||||
Belum ada transaksi baru.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted">Belum ada transaksi baru.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<a href="{{ route('admin.monitoring') }}" class="btn btn-sm btn-primary">
|
||||
Lihat Semua Data
|
||||
</a>
|
||||
<a href="{{ route('admin.monitoring') }}" class="btn btn-sm btn-primary">Lihat Semua Data</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -158,4 +145,129 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@section('js')
|
||||
{{-- Load library Chart.js --}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const labelTerlarisAsli = {!! json_encode($labelTerlaris) !!};
|
||||
const dataTerlaris = {!! json_encode($dataTerlaris) !!};
|
||||
|
||||
const labelMultiline = labelTerlarisAsli.map(function(label) {
|
||||
const words = label.split(' ');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
|
||||
words.forEach(word => {
|
||||
if ((currentLine + word).length > 15 && currentLine !== '') {
|
||||
lines.push(currentLine.trim());
|
||||
currentLine = word + ' ';
|
||||
} else {
|
||||
currentLine += word + ' ';
|
||||
}
|
||||
});
|
||||
lines.push(currentLine.trim());
|
||||
return lines;
|
||||
});
|
||||
|
||||
// --- GRAFIK PRODUK TERLARIS ---
|
||||
const ctxTerlaris = document.getElementById('chartTerlaris').getContext('2d');
|
||||
new Chart(ctxTerlaris, {
|
||||
type: 'bar',
|
||||
|
||||
data: {
|
||||
labels: labelMultiline,
|
||||
datasets: [{
|
||||
label: 'Total Terjual',
|
||||
data: dataTerlaris,
|
||||
backgroundColor: 'rgba(129, 196, 8, 0.8)',
|
||||
borderColor: '#81c408',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: function(context) {
|
||||
return labelTerlarisAsli[context[0].dataIndex];
|
||||
},
|
||||
label: function(context) {
|
||||
return context.raw + ' Kg';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
autoSkip: false,
|
||||
font: { size: 12 }
|
||||
},
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- GRAFIK KESTABILAN HARGA ---
|
||||
const ctxHarga = document.getElementById('chartHarga').getContext('2d');
|
||||
new Chart(ctxHarga, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: {!! json_encode($labelHarga) !!},
|
||||
datasets: [{
|
||||
label: 'Rata-rata Harga',
|
||||
data: {!! json_encode($dataHarga) !!},
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||||
borderColor: '#36A2EB',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
pointBorderColor: '#36A2EB',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 4,
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return 'Rp ' + context.raw.toLocaleString('id-ID');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
callback: function(value) { return 'Rp ' + value.toLocaleString('id-ID'); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
Loading…
Reference in New Issue