dashboard pasar
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Web\Dashboard;
|
||||
|
||||
use App\Enums\AksesPasar\PraktikFermentasi;
|
||||
use App\Enums\AksesPasar\SkorKesadaranPremium;
|
||||
use App\Enums\AksesPasar\SkorPengalaman;
|
||||
use App\Enums\KeparahanSerangan;
|
||||
use App\Enums\KondisiTanaman;
|
||||
use App\Enums\PenyakitTanaman;
|
||||
@@ -114,70 +116,196 @@ class SummaryController extends Controller
|
||||
'kecamatan_id',
|
||||
'desaKelurahan_id',
|
||||
'komoditas_id',
|
||||
'segmen',
|
||||
]),
|
||||
] + $filterData + $this->marketDashboardData($request));
|
||||
}
|
||||
|
||||
private function marketDashboardData(Request $request): array
|
||||
{
|
||||
if (! $request->filled('komoditas_id')) {
|
||||
return [
|
||||
'selectedKomoditas' => null,
|
||||
'priceCard' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$selectedKomoditas = Komoditas::query()
|
||||
->whereKey($request->komoditas_id)
|
||||
->first(['id', 'nama']);
|
||||
|
||||
if (! $selectedKomoditas) {
|
||||
return [
|
||||
'selectedKomoditas' => null,
|
||||
'priceCard' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$productCaption = $this->marketProductCaption($selectedKomoditas->nama);
|
||||
$productLabel = 'Buah segar hasil petik';
|
||||
$products = [
|
||||
['name' => 'Kakao', 'caption' => 'Harga Biji Kering', 'product' => 'Biji kering/Biji Kupas', 'variant' => 'success'],
|
||||
['name' => 'Vanili', 'caption' => 'Harga Buah Segar', 'product' => 'Buah segar hasil petik', 'variant' => 'primary'],
|
||||
['name' => 'Kopi', 'caption' => 'Harga Ceri Merah', 'product' => 'Buah segar hasil petik', 'variant' => 'warning'],
|
||||
['name' => 'Kemiri', 'caption' => 'Harga Gelondong', 'product' => 'Buah segar hasil petik', 'variant' => 'info'],
|
||||
];
|
||||
$commodities = Komoditas::query()
|
||||
->whereIn('nama', array_column($products, 'name'))
|
||||
->pluck('id', 'nama');
|
||||
|
||||
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||
$priceQuery = DB::table('harga_jual_panen')
|
||||
$prices = DB::table('harga_jual_panen')
|
||||
->join('petani', 'petani.id', '=', 'harga_jual_panen.petani_id')
|
||||
->whereIn('harga_jual_panen.petani_id', $petaniIds)
|
||||
->whereIn('petani.komoditas_id', $commodities->values())
|
||||
->where('harga_jual_panen.harga_panen_terakhir', '>', 0)
|
||||
->where('harga_jual_panen.bentuk_produk', $productLabel);
|
||||
|
||||
$averagePrice = (float) (clone $priceQuery)->avg('harga_jual_panen.harga_panen_terakhir');
|
||||
$totalRecords = (int) (clone $priceQuery)->count();
|
||||
->whereIn('harga_jual_panen.bentuk_produk', array_unique(array_column($products, 'product')))
|
||||
->select('petani.komoditas_id', 'harga_jual_panen.bentuk_produk')
|
||||
->selectRaw('AVG(harga_jual_panen.harga_panen_terakhir) as average_price, COUNT(*) as records')
|
||||
->groupBy('petani.komoditas_id', 'harga_jual_panen.bentuk_produk')
|
||||
->get();
|
||||
|
||||
return [
|
||||
'selectedKomoditas' => $selectedKomoditas,
|
||||
'priceCard' => [
|
||||
'value' => $averagePrice > 0
|
||||
? 'Rp '.number_format($averagePrice, 0, ',', '.')
|
||||
: 'Rp 0',
|
||||
'label' => 'Harga Jual',
|
||||
'caption' => $productCaption,
|
||||
'description' => 'Harga rata-rata buah segar hasil petik di tingkat petani',
|
||||
'records' => $totalRecords,
|
||||
'unit' => '/kg',
|
||||
'variant' => 'success',
|
||||
'icon' => 'tabler-cash',
|
||||
],
|
||||
'priceCards' => collect($products)->map(function (array $product) use ($commodities, $prices) {
|
||||
$commodityId = $commodities->get($product['name']);
|
||||
$price = $commodityId === null ? null : $prices->first(fn ($row) =>
|
||||
(int) $row->komoditas_id === (int) $commodityId
|
||||
&& $row->bentuk_produk === $product['product']
|
||||
);
|
||||
|
||||
return [
|
||||
'value' => 'Rp '.number_format((float) ($price?->average_price ?? 0), 0, ',', '.'),
|
||||
'label' => $product['name'].' - '.$product['caption'],
|
||||
'description' => 'Harga rata-rata panen terakhir di tingkat petani',
|
||||
'records' => (int) ($price?->records ?? 0),
|
||||
'unit' => '/kg',
|
||||
'variant' => $product['variant'],
|
||||
'icon' => 'tabler-cash',
|
||||
];
|
||||
})->all(),
|
||||
'marketCharts' => $this->marketChannelCharts($request),
|
||||
'priceTrend' => $this->marketPriceTrend($request),
|
||||
'premiumAwareness' => $this->marketPremiumAwareness($request),
|
||||
];
|
||||
}
|
||||
|
||||
private function marketProductCaption(string $komoditasName): string
|
||||
private function marketPriceTrend(Request $request): array
|
||||
{
|
||||
$normalized = str($komoditasName)->lower()->trim()->toString();
|
||||
$products = ['Buah segar hasil petik', 'Biji basah', 'Biji kering/Biji Kupas', 'Terfermentasi (Rp/kg)'];
|
||||
$periods = [
|
||||
'harga_dua_tahun_lalu' => 'Dua Tahun Lalu',
|
||||
'harga_tahun_lalu' => 'Tahun Lalu',
|
||||
'harga_panen_terakhir' => 'Panen Terakhir',
|
||||
];
|
||||
$commodities = Komoditas::query()
|
||||
->when($request->filled('komoditas_id'), fn ($query) => $query->where('id', $request->komoditas_id))
|
||||
->orderBy('nama')
|
||||
->get(['id', 'nama']);
|
||||
|
||||
return match (true) {
|
||||
str_contains($normalized, 'kakao') => 'Harga Buah Kakao',
|
||||
str_contains($normalized, 'vanili') => 'Harga Curing',
|
||||
str_contains($normalized, 'kopi') => 'Harga Ceri Merah',
|
||||
str_contains($normalized, 'kemiri') => 'Harga Gelondong',
|
||||
default => 'Harga Buah Segar',
|
||||
};
|
||||
$query = DB::table('harga_jual_panen')
|
||||
->join('petani', 'petani.id', '=', 'harga_jual_panen.petani_id')
|
||||
->whereIn('harga_jual_panen.petani_id', $this->filteredPetaniIdsQuery($request))
|
||||
->whereIn('petani.komoditas_id', $commodities->pluck('id'))
|
||||
->whereIn('harga_jual_panen.bentuk_produk', $products)
|
||||
->select('petani.komoditas_id', 'harga_jual_panen.bentuk_produk')
|
||||
->groupBy('petani.komoditas_id', 'harga_jual_panen.bentuk_produk');
|
||||
|
||||
foreach (array_keys($periods) as $field) {
|
||||
$query->selectRaw("AVG(CASE WHEN harga_jual_panen.{$field} > 0 THEN harga_jual_panen.{$field} END) as {$field}");
|
||||
}
|
||||
|
||||
$prices = $query->get()->groupBy('komoditas_id');
|
||||
|
||||
return [
|
||||
'periods' => array_values($periods),
|
||||
'commodities' => $commodities->map(function ($commodity) use ($products, $periods, $prices) {
|
||||
$rows = $prices->get($commodity->id, collect())->keyBy('bentuk_produk');
|
||||
|
||||
return [
|
||||
'id' => $commodity->id,
|
||||
'name' => $commodity->nama,
|
||||
'series' => collect($products)->map(function ($product) use ($periods, $rows) {
|
||||
$row = $rows->get($product);
|
||||
|
||||
return [
|
||||
'name' => $product === 'Terfermentasi (Rp/kg)' ? 'Terfermentasi' : $product,
|
||||
'data' => array_map(fn ($field) => isset($row->{$field}) ? round((float) $row->{$field}, 2) : null, array_keys($periods)),
|
||||
];
|
||||
})->all(),
|
||||
];
|
||||
})->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function marketChannelCharts(Request $request): array
|
||||
{
|
||||
$rows = DB::table('akses_pasar')
|
||||
->whereNull('deleted_at')
|
||||
->whereIn('petani_id', $this->filteredPetaniIdsQuery($request))
|
||||
->orderBy('id')
|
||||
->get(['petani_id', 'tujuan_penjualan', 'sifat_hubungan_bisnis', 'frekuensi_penjualan', 'skor_pengalaman'])
|
||||
->unique('petani_id');
|
||||
|
||||
$channels = ['Tengkulak/pedagang lokal', 'Koperasi', 'BUMDes', 'Langsung ke pembeli/eksportir', 'Lainnya'];
|
||||
$relationships = [
|
||||
'Bebas, bisa pindah pembeli kapan saja' => 'Bebas pindah pembeli',
|
||||
'Langganan tetap, tapi tidak terikat' => 'Langganan tetap',
|
||||
'Berbasis hutang, harus jual ke sana karena ada pinjaman' => 'Berbasis hutang',
|
||||
'Titip jual,dibayar setelah produk terjual' => 'Titip jual',
|
||||
'Ada komitmen volume, sudah ada kesepakatan jumlah' => 'Komitmen volume',
|
||||
'Semi-kontrak, ada kesepakatan harga tertentu' => 'Semi-kontrak',
|
||||
];
|
||||
$timing = ['Langsung setelah panen', 'Ditumpuk dulu beberapa waktu', 'Saat butuh uang mendesak'];
|
||||
$experience = collect(SkorPengalaman::options())
|
||||
->mapWithKeys(fn ($option) => [$option['value'] => $option['label']])
|
||||
->all();
|
||||
|
||||
return [
|
||||
'saluranPenjualan' => $this->marketResponseChart($rows, 'tujuan_penjualan', array_combine($channels, $channels), true),
|
||||
'hubunganPembeli' => $this->marketResponseChart($rows, 'sifat_hubungan_bisnis', $relationships),
|
||||
'waktuPenjualan' => $this->marketResponseChart($rows, 'frekuensi_penjualan', array_combine($timing, $timing)),
|
||||
'aksesSaluranLain' => $this->marketResponseChart($rows, 'skor_pengalaman', $experience),
|
||||
];
|
||||
}
|
||||
|
||||
private function marketPremiumAwareness(Request $request): array
|
||||
{
|
||||
$rows = DB::table('akses_pasar')
|
||||
->whereNull('deleted_at')
|
||||
->whereIn('petani_id', $this->filteredPetaniIdsQuery($request))
|
||||
->orderBy('id')
|
||||
->get(['petani_id', 'skor_kesadaran_premium'])
|
||||
->unique('petani_id');
|
||||
$options = collect(SkorKesadaranPremium::options())
|
||||
->mapWithKeys(fn ($option) => [$option['value'] => $option['label']])
|
||||
->all();
|
||||
$chart = $this->marketResponseChart($rows, 'skor_kesadaran_premium', $options);
|
||||
$chart['fullLabels'] = [
|
||||
'Belum pernah mendengar sama sekali',
|
||||
'Pernah mendengar tapi belum paham persyaratannya',
|
||||
'Pernah mendengar dan sedikit memahami persyaratannya',
|
||||
'Sudah sangat memahami persyaratannya, tapi belum pernah mengakses pasar premium',
|
||||
'Sudah mengakses dan mendapat harga premium secara konsisten',
|
||||
];
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
private function marketResponseChart($rows, string $field, array $options, bool $multiple = false): array
|
||||
{
|
||||
$counts = array_fill_keys(array_keys($options), 0);
|
||||
$respondents = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$values = $multiple ? $this->arrayValues($row->{$field}) : [$row->{$field}];
|
||||
$answers = [];
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (is_string($value) || is_int($value)) {
|
||||
$value = is_string($value) ? trim($value) : $value;
|
||||
if (array_key_exists($value, $options)) {
|
||||
$answers[$value] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($answers === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$respondents++;
|
||||
foreach (array_keys($answers) as $answer) {
|
||||
$counts[$answer]++;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'labels' => array_values($options),
|
||||
'fullLabels' => $field === 'skor_pengalaman' ? array_values($options) : array_keys($options),
|
||||
'series' => array_values($counts),
|
||||
'respondents' => $respondents,
|
||||
'percentages' => array_map(fn ($count) => $respondents > 0 ? round($count / $respondents * 100, 1) : 0, array_values($counts)),
|
||||
];
|
||||
}
|
||||
|
||||
private function productionDashboardData(Request $request): array
|
||||
|
||||
Reference in New Issue
Block a user