dashboard produksi
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Web\Dashboard;
|
namespace App\Http\Controllers\Web\Dashboard;
|
||||||
|
|
||||||
|
use App\Enums\AksesPasar\PraktikFermentasi;
|
||||||
use App\Enums\KeparahanSerangan;
|
use App\Enums\KeparahanSerangan;
|
||||||
use App\Enums\KondisiTanaman;
|
use App\Enums\KondisiTanaman;
|
||||||
use App\Enums\PenyakitTanaman;
|
use App\Enums\PenyakitTanaman;
|
||||||
@@ -9,9 +10,14 @@ use App\Enums\PraktikBudidaya\FrekuensiPemangkasanUtama;
|
|||||||
use App\Enums\PraktikBudidaya\FrekuensiSanitasiLahan;
|
use App\Enums\PraktikBudidaya\FrekuensiSanitasiLahan;
|
||||||
use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit;
|
use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit;
|
||||||
use App\Enums\PraktikBudidaya\PemberianPupuk;
|
use App\Enums\PraktikBudidaya\PemberianPupuk;
|
||||||
|
use App\Enums\PraktikBudidaya\PraktikSortasi;
|
||||||
use App\Enums\SeranganHama;
|
use App\Enums\SeranganHama;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Pertanian\Komoditas;
|
use App\Models\Pertanian\Komoditas;
|
||||||
|
use App\Models\Wilayah\DesaKelurahan;
|
||||||
|
use App\Models\Wilayah\KabupatenKota;
|
||||||
|
use App\Models\Wilayah\Kecamatan;
|
||||||
|
use App\Models\Wilayah\Provinsi;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
@@ -49,6 +55,89 @@ class SummaryController extends Controller
|
|||||||
] + $filterData + $this->productionDashboardData($request));
|
] + $filterData + $this->productionDashboardData($request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function pasar(Request $request)
|
||||||
|
{
|
||||||
|
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = 'Pasar Dashboard';
|
||||||
|
$filterData = app(IndeksPetaniController::class)->contentData($request);
|
||||||
|
|
||||||
|
return view('dashboard.pasar', [
|
||||||
|
'title' => $title,
|
||||||
|
'dashboardFilters' => $request->only([
|
||||||
|
'provinsi_id',
|
||||||
|
'kabupatenKota_id',
|
||||||
|
'kecamatan_id',
|
||||||
|
'desaKelurahan_id',
|
||||||
|
'komoditas_id',
|
||||||
|
]),
|
||||||
|
] + $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';
|
||||||
|
|
||||||
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
|
$priceQuery = DB::table('harga_jual_panen')
|
||||||
|
->whereIn('harga_jual_panen.petani_id', $petaniIds)
|
||||||
|
->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();
|
||||||
|
|
||||||
|
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',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function marketProductCaption(string $komoditasName): string
|
||||||
|
{
|
||||||
|
$normalized = str($komoditasName)->lower()->trim()->toString();
|
||||||
|
|
||||||
|
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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private function productionDashboardData(Request $request): array
|
private function productionDashboardData(Request $request): array
|
||||||
{
|
{
|
||||||
$lahanQuery = $this->filteredLahanQuery($request);
|
$lahanQuery = $this->filteredLahanQuery($request);
|
||||||
@@ -96,6 +185,8 @@ class SummaryController extends Controller
|
|||||||
$avgLuasPerPetani = $landMetrics['ratarata_luas_lahan'];
|
$avgLuasPerPetani = $landMetrics['ratarata_luas_lahan'];
|
||||||
$productivityCards = $this->productivityKpiCards($lahanIds);
|
$productivityCards = $this->productivityKpiCards($lahanIds);
|
||||||
$praktikBudidaya = $this->filteredPraktikBudidaya($request);
|
$praktikBudidaya = $this->filteredPraktikBudidaya($request);
|
||||||
|
$surveyPanen = $this->filteredSurveyPanen($request);
|
||||||
|
$aksesPasar = $this->filteredAksesPasar($request);
|
||||||
|
|
||||||
$kpiCards = [
|
$kpiCards = [
|
||||||
[
|
[
|
||||||
@@ -119,23 +210,24 @@ class SummaryController extends Controller
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'kpiCards' => $kpiCards,
|
'kpiCards' => $kpiCards,
|
||||||
|
'mapData' => $this->productionMapData($request),
|
||||||
'chartData' => [
|
'chartData' => [
|
||||||
'umurTanaman' => $this->ageBucketChart($healthSource, 'umur_tanaman'),
|
'umurTanaman' => $this->ageBucketByCommodityChart($healthSource, $lahan, 'umur_tanaman'),
|
||||||
'jumlahTanamanPerKomoditas' => $this->plantCountByCommodityChart($tanaman, $lahan),
|
'jumlahTanamanPerKomoditas' => $this->plantCountByCommodityChart($tanaman, $lahan),
|
||||||
'kondisiTanaman' => $this->attributeChart($conditionSource, 'kondisi_tanaman', fn ($value) => $this->conditionLabel($value)),
|
'kondisiTanaman' => $this->attributeChart($conditionSource, 'kondisi_tanaman', fn ($value) => $this->conditionLabel($value)),
|
||||||
'keparahanSerangan' => $this->attributeChart($healthSource, 'keparahan_serangan', fn ($value) => $this->severityLabel($value)),
|
'keparahanSerangan' => $this->attributeChart($healthSource, 'keparahan_serangan', fn ($value) => $this->severityLabel($value)),
|
||||||
'gejalaSerangan' => $this->symptomChart($healthSource),
|
'gejalaSerangan' => $this->symptomChart($healthSource),
|
||||||
'intensitasPemangkasan' => $this->enumOptionChart($praktikBudidaya, 'frekuensi_pemangkasan_utama', FrekuensiPemangkasanUtama::class),
|
'intensitasPemangkasan' => $this->enumOptionByCommodityChart($praktikBudidaya, 'frekuensi_pemangkasan_utama', FrekuensiPemangkasanUtama::class),
|
||||||
'intensitasPemupukan' => $this->enumOptionChart($praktikBudidaya, 'pemberian_pupuk', PemberianPupuk::class),
|
'intensitasPemupukan' => $this->enumOptionByCommodityChart($praktikBudidaya, 'pemberian_pupuk', PemberianPupuk::class),
|
||||||
'metodePenangananHamaPenyakit' => $this->enumOptionChart($praktikBudidaya, 'metode_penanganan_opt', MetodePenangananHamaPenyakit::class),
|
'metodePenangananHamaPenyakit' => $this->enumOptionByCommodityChart($praktikBudidaya, 'metode_penanganan_opt', MetodePenangananHamaPenyakit::class),
|
||||||
'intensitasPembersihanLahan' => $this->enumOptionChart($praktikBudidaya, 'frekuensi_sanitasi_lahan', FrekuensiSanitasiLahan::class),
|
'intensitasPembersihanLahan' => $this->enumOptionByCommodityChart($praktikBudidaya, 'frekuensi_sanitasi_lahan', FrekuensiSanitasiLahan::class),
|
||||||
'sumberAirKebun' => $this->arrayOptionChart(
|
'sumberAirKebun' => $this->arrayOptionByCommodityChart(
|
||||||
$praktikBudidaya,
|
$praktikBudidaya,
|
||||||
'sumber_air',
|
'sumber_air',
|
||||||
['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja', 'Lainnya'],
|
['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja', 'Lainnya'],
|
||||||
fn ($value) => in_array($value, ['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja'], true) ? $value : 'Lainnya',
|
fn ($value) => in_array($value, ['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja'], true) ? $value : 'Lainnya',
|
||||||
),
|
),
|
||||||
'kebiasaanPraktikBaik' => $this->arrayOptionChart(
|
'kebiasaanPraktikBaik' => $this->arrayOptionByCommodityChart(
|
||||||
$praktikBudidaya,
|
$praktikBudidaya,
|
||||||
'kebiasaan_konservasi_tanah',
|
'kebiasaan_konservasi_tanah',
|
||||||
[
|
[
|
||||||
@@ -149,10 +241,387 @@ class SummaryController extends Controller
|
|||||||
? 'Membuat pematang/tanggul tanah di lahan miring supaya tanah tidak longsor ke bawah'
|
? 'Membuat pematang/tanggul tanah di lahan miring supaya tanah tidak longsor ke bawah'
|
||||||
: $value,
|
: $value,
|
||||||
),
|
),
|
||||||
|
'praktikSortasiPanen' => $this->enumOptionChart($surveyPanen, 'praktik_sortasi', PraktikSortasi::class),
|
||||||
|
'praktikFermentasiPascapanen' => $this->fermentationPracticeChart($aksesPasar),
|
||||||
|
'produktivitasAktualPerKomoditas' => $this->actualProductivityByCommodityChart($request),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function productionMapData(Request $request): array
|
||||||
|
{
|
||||||
|
$levels = $this->productionMapLevelData($request);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'filters' => [
|
||||||
|
'provinsi' => (string) $request->input('provinsi_id', ''),
|
||||||
|
'kabupaten' => (string) $request->input('kabupatenKota_id', ''),
|
||||||
|
'kecamatan' => (string) $request->input('kecamatan_id', ''),
|
||||||
|
'desa' => (string) $request->input('desaKelurahan_id', ''),
|
||||||
|
],
|
||||||
|
'levels' => $levels,
|
||||||
|
'cultivationLevels' => $this->productionCultivationMapLevelData($request, $levels),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function productionMapLevelData(Request $request): array
|
||||||
|
{
|
||||||
|
$lahanSummaryByDesa = (clone $this->filteredLahanQuery($request))
|
||||||
|
->leftJoin('profiles', 'profiles.id', '=', 'map_lahan.profile_id')
|
||||||
|
->leftJoin('petani', 'petani.id', '=', 'map_lahan.petani_id')
|
||||||
|
->tap(fn ($query) => $this->whereActiveMapOwner($query))
|
||||||
|
->whereNotNull('map_lahan.desa_kelurahan_id')
|
||||||
|
->select('map_lahan.desa_kelurahan_id')
|
||||||
|
->selectRaw('COUNT(map_lahan.id) as total_lahan')
|
||||||
|
->selectRaw('COALESCE(SUM(map_lahan.luas_lahan), 0) as total_luas')
|
||||||
|
->groupBy('map_lahan.desa_kelurahan_id')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$desaIds = $lahanSummaryByDesa->pluck('desa_kelurahan_id')->filter()->unique()->values();
|
||||||
|
|
||||||
|
if ($request->filled('desaKelurahan_id')) {
|
||||||
|
$desaIds->push($request->desaKelurahan_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$desaList = DesaKelurahan::query()
|
||||||
|
->select('id', 'nama', 'path', 'kecamatan_id')
|
||||||
|
->when(
|
||||||
|
$desaIds->isNotEmpty(),
|
||||||
|
fn ($query) => $query->whereIn('id', $desaIds->unique()->values()),
|
||||||
|
fn ($query) => $query->whereRaw('1 = 0')
|
||||||
|
)
|
||||||
|
->with([
|
||||||
|
'kecamatan:id,nama,path,kabupaten_kota_id',
|
||||||
|
'kecamatan.kabupatenKota:id,nama,path,provinsi_id',
|
||||||
|
'kecamatan.kabupatenKota.provinsi:id,nama,path',
|
||||||
|
])
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
|
||||||
|
$geoProvinsi = [];
|
||||||
|
$geoKabupaten = [];
|
||||||
|
$geoKecamatan = [];
|
||||||
|
$geoDesa = [];
|
||||||
|
|
||||||
|
foreach ($lahanSummaryByDesa as $summary) {
|
||||||
|
$desa = $desaList->get($summary->desa_kelurahan_id);
|
||||||
|
$kecamatan = $desa?->kecamatan;
|
||||||
|
$kabupaten = $kecamatan?->kabupatenKota;
|
||||||
|
$provinsi = $kabupaten?->provinsi;
|
||||||
|
$totalLahan = (int) $summary->total_lahan;
|
||||||
|
$totalLuas = (float) $summary->total_luas;
|
||||||
|
|
||||||
|
if ($provinsi && $provinsi->path) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoProvinsi, $provinsi->id, null, $provinsi->nama, $provinsi->path, $totalLahan, $totalLuas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($kabupaten && $kabupaten->path) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoKabupaten, $kabupaten->id, $provinsi?->id, $kabupaten->nama, $kabupaten->path, $totalLahan, $totalLuas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($kecamatan && $kecamatan->path) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoKecamatan, $kecamatan->id, $kabupaten?->id, $kecamatan->nama, $kecamatan->path, $totalLahan, $totalLuas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($desa && $desa->path) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoDesa, $desa->id, $kecamatan?->id, $desa->nama, $desa->path, $totalLahan, $totalLuas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ensureSelectedProductionRegionOnMap($request, $geoProvinsi, $geoKabupaten, $geoKecamatan, $geoDesa);
|
||||||
|
|
||||||
|
$mapLahan = (clone $this->filteredLahanQuery($request))
|
||||||
|
->whereNotNull('map_lahan.path')
|
||||||
|
->leftJoin('profiles', 'profiles.id', '=', 'map_lahan.profile_id')
|
||||||
|
->leftJoin('petani', 'petani.id', '=', 'map_lahan.petani_id')
|
||||||
|
->leftJoin('master_komoditas', 'master_komoditas.id', '=', 'map_lahan.komoditas_id')
|
||||||
|
->tap(fn ($query) => $this->whereActiveMapOwner($query))
|
||||||
|
->orderByDesc('map_lahan.updated_at')
|
||||||
|
->limit(500)
|
||||||
|
->get([
|
||||||
|
'map_lahan.id',
|
||||||
|
'map_lahan.desa_kelurahan_id',
|
||||||
|
'map_lahan.path',
|
||||||
|
'map_lahan.luas_lahan',
|
||||||
|
'map_lahan.jumlah_tanaman',
|
||||||
|
'map_lahan.umur_tanaman',
|
||||||
|
'map_lahan.serangan_hama',
|
||||||
|
'map_lahan.penyakit_tanaman',
|
||||||
|
'map_lahan.jenis_hama',
|
||||||
|
'map_lahan.jenis_penyakit',
|
||||||
|
'profiles.nama as profile_nama',
|
||||||
|
'petani.nama as petani_nama',
|
||||||
|
'master_komoditas.nama as komoditas_nama',
|
||||||
|
]);
|
||||||
|
$lahanIds = $mapLahan->pluck('id')->filter()->values();
|
||||||
|
$tanamanByLahan = DB::table('map_tanaman')
|
||||||
|
->whereNull('map_tanaman.deleted_at')
|
||||||
|
->when($lahanIds->isNotEmpty(), fn ($query) => $query->whereIn('map_tanaman.lahan_id', $lahanIds))
|
||||||
|
->when($lahanIds->isEmpty(), fn ($query) => $query->whereRaw('1 = 0'))
|
||||||
|
->leftJoin('master_komoditas', 'master_komoditas.id', '=', 'map_tanaman.komoditas_id')
|
||||||
|
->get([
|
||||||
|
'map_tanaman.lahan_id',
|
||||||
|
'map_tanaman.umur_tanaman',
|
||||||
|
'map_tanaman.serangan_hama',
|
||||||
|
'map_tanaman.penyakit_tanaman',
|
||||||
|
'map_tanaman.jenis_hama',
|
||||||
|
'map_tanaman.jenis_penyakit',
|
||||||
|
'master_komoditas.nama as komoditas_nama',
|
||||||
|
])
|
||||||
|
->groupBy('lahan_id');
|
||||||
|
|
||||||
|
$geoLahan = $mapLahan
|
||||||
|
->filter(fn ($lahan) => $this->hasMapPolygon($lahan->path))
|
||||||
|
->map(function ($lahan) use ($tanamanByLahan) {
|
||||||
|
$tanamanRows = $tanamanByLahan->get($lahan->id, collect());
|
||||||
|
$komoditas = $this->productionCommodityDetails($lahan, $tanamanRows);
|
||||||
|
$opt = $this->productionOptDetails($lahan, $tanamanRows);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $lahan->id,
|
||||||
|
'parent_id' => $lahan->desa_kelurahan_id,
|
||||||
|
'polygon' => $lahan->path,
|
||||||
|
'nama_petani' => $lahan->petani_nama ?? $lahan->profile_nama ?? 'N/A',
|
||||||
|
'luas' => (float) $lahan->luas_lahan,
|
||||||
|
'komoditas' => $komoditas,
|
||||||
|
'opt' => $opt,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'provinsi' => array_values($geoProvinsi),
|
||||||
|
'kabupaten' => array_values($geoKabupaten),
|
||||||
|
'kecamatan' => array_values($geoKecamatan),
|
||||||
|
'desa' => array_values($geoDesa),
|
||||||
|
'lahan' => $geoLahan,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function productionCultivationMapLevelData(Request $request, array $baseLevels): array
|
||||||
|
{
|
||||||
|
$mapLahan = (clone $this->filteredLahanQuery($request))
|
||||||
|
->whereNotNull('map_lahan.path')
|
||||||
|
->leftJoin('profiles', 'profiles.id', '=', 'map_lahan.profile_id')
|
||||||
|
->leftJoin('petani', 'petani.id', '=', 'map_lahan.petani_id')
|
||||||
|
->tap(fn ($query) => $this->whereActiveMapOwner($query))
|
||||||
|
->orderByDesc('map_lahan.updated_at')
|
||||||
|
->limit(500)
|
||||||
|
->get([
|
||||||
|
'map_lahan.id',
|
||||||
|
'map_lahan.desa_kelurahan_id',
|
||||||
|
'map_lahan.path',
|
||||||
|
'map_lahan.luas_lahan',
|
||||||
|
'map_lahan.petani_id',
|
||||||
|
'profiles.nama as profile_nama',
|
||||||
|
'petani.nama as petani_nama',
|
||||||
|
]);
|
||||||
|
$petaniIds = $mapLahan->pluck('petani_id')->filter()->unique()->values();
|
||||||
|
$practiceByPetani = DB::table('praktik_budidaya')
|
||||||
|
->whereNull('praktik_budidaya.deleted_at')
|
||||||
|
->when($petaniIds->isNotEmpty(), fn ($query) => $query->whereIn('praktik_budidaya.petani_id', $petaniIds))
|
||||||
|
->when($petaniIds->isEmpty(), fn ($query) => $query->whereRaw('1 = 0'))
|
||||||
|
->orderByDesc('praktik_budidaya.updated_at')
|
||||||
|
->get([
|
||||||
|
'praktik_budidaya.petani_id',
|
||||||
|
'praktik_budidaya.frekuensi_pemangkasan_utama',
|
||||||
|
'praktik_budidaya.pemberian_pupuk',
|
||||||
|
'praktik_budidaya.kebiasaan_konservasi_tanah',
|
||||||
|
])
|
||||||
|
->groupBy('petani_id')
|
||||||
|
->map(fn ($items) => $items->first());
|
||||||
|
|
||||||
|
$geoLahan = $mapLahan
|
||||||
|
->filter(fn ($lahan) => $this->hasMapPolygon($lahan->path))
|
||||||
|
->map(function ($lahan) use ($practiceByPetani) {
|
||||||
|
$practice = $practiceByPetani->get($lahan->petani_id);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $lahan->id,
|
||||||
|
'parent_id' => $lahan->desa_kelurahan_id,
|
||||||
|
'polygon' => $lahan->path,
|
||||||
|
'nama_petani' => $lahan->petani_nama ?? $lahan->profile_nama ?? 'N/A',
|
||||||
|
'luas' => (float) $lahan->luas_lahan,
|
||||||
|
'pemangkasan' => $this->enumLabel(FrekuensiPemangkasanUtama::class, $practice->frekuensi_pemangkasan_utama ?? null, 'Belum ada data'),
|
||||||
|
'pemupukan' => $this->enumLabel(PemberianPupuk::class, $practice->pemberian_pupuk ?? null, 'Belum ada data'),
|
||||||
|
'praktik_baik' => $this->arrayValues($practice->kebiasaan_konservasi_tanah ?? null),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'provinsi' => $baseLevels['provinsi'] ?? [],
|
||||||
|
'kabupaten' => $baseLevels['kabupaten'] ?? [],
|
||||||
|
'kecamatan' => $baseLevels['kecamatan'] ?? [],
|
||||||
|
'desa' => $baseLevels['desa'] ?? [],
|
||||||
|
'lahan' => $geoLahan,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function whereActiveMapOwner($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($query) {
|
||||||
|
$query
|
||||||
|
->where(function ($query) {
|
||||||
|
$query
|
||||||
|
->whereNotNull('map_lahan.petani_id')
|
||||||
|
->whereNotNull('petani.id')
|
||||||
|
->whereNull('petani.deleted_at');
|
||||||
|
})
|
||||||
|
->orWhere(function ($query) {
|
||||||
|
$query
|
||||||
|
->whereNull('map_lahan.petani_id')
|
||||||
|
->whereNotNull('map_lahan.profile_id')
|
||||||
|
->whereNotNull('profiles.id')
|
||||||
|
->whereNull('profiles.deleted_at');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function productionCommodityDetails($lahan, $tanamanRows): array
|
||||||
|
{
|
||||||
|
$details = $tanamanRows
|
||||||
|
->groupBy(fn ($row) => $row->komoditas_nama ?: ($lahan->komoditas_nama ?: 'Belum ditentukan'))
|
||||||
|
->map(function ($rows, string $name) {
|
||||||
|
$ages = $rows
|
||||||
|
->pluck('umur_tanaman')
|
||||||
|
->filter(fn ($age) => is_numeric($age))
|
||||||
|
->map(fn ($age) => (float) $age);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'nama' => $name,
|
||||||
|
'jumlah' => $rows->count(),
|
||||||
|
'umur' => $ages->isNotEmpty() ? round((float) $ages->avg(), 1) : null,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
|
if ($details->isEmpty()) {
|
||||||
|
$details->push([
|
||||||
|
'nama' => $lahan->komoditas_nama ?: 'Belum ditentukan',
|
||||||
|
'jumlah' => (int) ($lahan->jumlah_tanaman ?? 0),
|
||||||
|
'umur' => is_numeric($lahan->umur_tanaman) ? round((float) $lahan->umur_tanaman, 1) : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $details->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function productionOptDetails($lahan, $tanamanRows): array
|
||||||
|
{
|
||||||
|
return $tanamanRows
|
||||||
|
->flatMap(fn ($row) => $this->optValuesFromRow($row))
|
||||||
|
->merge($this->optValuesFromRow($lahan))
|
||||||
|
->filter()
|
||||||
|
->unique(fn ($item) => strtolower((string) $item))
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function optValuesFromRow($row): array
|
||||||
|
{
|
||||||
|
$hamaDetails = collect($this->arrayValues($row->jenis_hama ?? null))->filter();
|
||||||
|
$penyakitDetails = collect($this->arrayValues($row->jenis_penyakit ?? null))->filter();
|
||||||
|
|
||||||
|
return collect()
|
||||||
|
->merge($hamaDetails->isNotEmpty()
|
||||||
|
? $hamaDetails
|
||||||
|
: [$this->enumLabel(SeranganHama::class, $row->serangan_hama ?? null)])
|
||||||
|
->merge($penyakitDetails->isNotEmpty()
|
||||||
|
? $penyakitDetails
|
||||||
|
: [$this->enumLabel(PenyakitTanaman::class, $row->penyakit_tanaman ?? null)])
|
||||||
|
->filter(fn ($value) => $value !== null && $value !== '')
|
||||||
|
->map(fn ($value) => str((string) $value)->replace('_', ' ')->title()->toString())
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function accumulateProductionGeoLevel(array &$bucket, int|string $id, int|string|null $parentId, string $name, mixed $polygon, int $totalLahan, float $totalLuas): void
|
||||||
|
{
|
||||||
|
if (! isset($bucket[$id])) {
|
||||||
|
$bucket[$id] = [
|
||||||
|
'id' => $id,
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'nama' => $name,
|
||||||
|
'polygon' => $polygon,
|
||||||
|
'total_lahan' => 0,
|
||||||
|
'total_luas' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$bucket[$id]['total_lahan'] += $totalLahan;
|
||||||
|
$bucket[$id]['total_luas'] += $totalLuas;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureSelectedProductionRegionOnMap(Request $request, array &$geoProvinsi, array &$geoKabupaten, array &$geoKecamatan, array &$geoDesa): void
|
||||||
|
{
|
||||||
|
if ($request->filled('provinsi_id')) {
|
||||||
|
$provinsi = Provinsi::query()->find($request->provinsi_id);
|
||||||
|
|
||||||
|
if ($provinsi && $provinsi->path && ! isset($geoProvinsi[$provinsi->id])) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoProvinsi, $provinsi->id, null, $provinsi->nama, $provinsi->path, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('kabupatenKota_id')) {
|
||||||
|
$kabupaten = KabupatenKota::query()->with('provinsi:id,nama,path')->find($request->kabupatenKota_id);
|
||||||
|
|
||||||
|
if ($kabupaten && $kabupaten->path && ! isset($geoKabupaten[$kabupaten->id])) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoKabupaten, $kabupaten->id, $kabupaten->provinsi_id, $kabupaten->nama, $kabupaten->path, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($kabupaten?->provinsi && $kabupaten->provinsi->path && ! isset($geoProvinsi[$kabupaten->provinsi->id])) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoProvinsi, $kabupaten->provinsi->id, null, $kabupaten->provinsi->nama, $kabupaten->provinsi->path, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('kecamatan_id')) {
|
||||||
|
$kecamatan = Kecamatan::query()->find($request->kecamatan_id);
|
||||||
|
|
||||||
|
if ($kecamatan && $kecamatan->path && ! isset($geoKecamatan[$kecamatan->id])) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoKecamatan, $kecamatan->id, $kecamatan->kabupaten_kota_id, $kecamatan->nama, $kecamatan->path, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('desaKelurahan_id')) {
|
||||||
|
$desa = DesaKelurahan::query()->find($request->desaKelurahan_id);
|
||||||
|
|
||||||
|
if ($desa && $desa->path && ! isset($geoDesa[$desa->id])) {
|
||||||
|
$this->accumulateProductionGeoLevel($geoDesa, $desa->id, $desa->kecamatan_id, $desa->nama, $desa->path, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasMapPolygon(mixed $path): bool
|
||||||
|
{
|
||||||
|
return ! empty($this->normalizeMapPolygon($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeMapPolygon(mixed $path): mixed
|
||||||
|
{
|
||||||
|
if (is_array($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_string($path) || blank($path) || strtolower($path) === 'null') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = $path;
|
||||||
|
|
||||||
|
for ($i = 0; $i < 3 && is_string($decoded); $i++) {
|
||||||
|
$decoded = json_decode($decoded, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decoded === 'null' ? null : $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
private function filteredPraktikBudidaya(Request $request)
|
private function filteredPraktikBudidaya(Request $request)
|
||||||
{
|
{
|
||||||
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
@@ -160,13 +629,53 @@ class SummaryController extends Controller
|
|||||||
return DB::table('praktik_budidaya')
|
return DB::table('praktik_budidaya')
|
||||||
->whereNull('praktik_budidaya.deleted_at')
|
->whereNull('praktik_budidaya.deleted_at')
|
||||||
->whereIn('praktik_budidaya.petani_id', $petaniIds)
|
->whereIn('praktik_budidaya.petani_id', $petaniIds)
|
||||||
|
->leftJoin('petani as praktik_petani', 'praktik_petani.id', '=', 'praktik_budidaya.petani_id')
|
||||||
|
->leftJoin('master_komoditas as praktik_komoditas', 'praktik_komoditas.id', '=', 'praktik_petani.komoditas_id')
|
||||||
->get([
|
->get([
|
||||||
'frekuensi_pemangkasan_utama',
|
'praktik_budidaya.frekuensi_pemangkasan_utama',
|
||||||
'pemberian_pupuk',
|
'praktik_budidaya.pemberian_pupuk',
|
||||||
'metode_penanganan_opt',
|
'praktik_budidaya.metode_penanganan_opt',
|
||||||
'frekuensi_sanitasi_lahan',
|
'praktik_budidaya.frekuensi_sanitasi_lahan',
|
||||||
'sumber_air',
|
'praktik_budidaya.sumber_air',
|
||||||
'kebiasaan_konservasi_tanah',
|
'praktik_budidaya.kebiasaan_konservasi_tanah',
|
||||||
|
'praktik_komoditas.nama as komoditas_nama',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filteredSurveyPanen(Request $request)
|
||||||
|
{
|
||||||
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
|
|
||||||
|
return DB::table('survey_panen')
|
||||||
|
->whereNull('survey_panen.deleted_at')
|
||||||
|
->whereIn('survey_panen.petani_id', $petaniIds)
|
||||||
|
->get(['praktik_sortasi']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filteredAksesPasar(Request $request)
|
||||||
|
{
|
||||||
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
|
|
||||||
|
return DB::table('akses_pasar')
|
||||||
|
->whereNull('akses_pasar.deleted_at')
|
||||||
|
->whereIn('akses_pasar.petani_id', $petaniIds)
|
||||||
|
->get(['praktik_fermentasi']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filteredSurveyProduktivitas(Request $request)
|
||||||
|
{
|
||||||
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
|
|
||||||
|
return DB::table('survey_produktivitas')
|
||||||
|
->whereNull('survey_produktivitas.deleted_at')
|
||||||
|
->whereIn('survey_produktivitas.petani_id', $petaniIds)
|
||||||
|
->when($request->filled('komoditas_id'), fn ($query) => $query->where('survey_produktivitas.komoditas_id', $request->komoditas_id))
|
||||||
|
->leftJoin('master_komoditas', 'master_komoditas.id', '=', 'survey_produktivitas.komoditas_id')
|
||||||
|
->get([
|
||||||
|
'survey_produktivitas.komoditas_id',
|
||||||
|
'survey_produktivitas.kuantitas',
|
||||||
|
'survey_produktivitas.satuan',
|
||||||
|
'master_komoditas.nama as komoditas_nama',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +776,54 @@ class SummaryController extends Controller
|
|||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function actualProductivityByCommodityChart(Request $request): array
|
||||||
|
{
|
||||||
|
$preferredOrder = ['Kakao', 'Vanili', 'Kemiri', 'Kopi'];
|
||||||
|
$selectedCommodities = Komoditas::query()
|
||||||
|
->whereNull('deleted_at')
|
||||||
|
->whereIn('nama', $preferredOrder)
|
||||||
|
->get(['id', 'nama'])
|
||||||
|
->sortBy(fn ($item) => array_search($item->nama, $preferredOrder, true))
|
||||||
|
->values();
|
||||||
|
|
||||||
|
$petaniIds = $this->filteredPetaniIdsQuery($request);
|
||||||
|
|
||||||
|
// Aggregate separately so multiple plots do not duplicate a farmer's seasonal harvest.
|
||||||
|
$landAreaByCommodity = DB::table('map_lahan')
|
||||||
|
->join('petani', 'petani.id', '=', 'map_lahan.petani_id')
|
||||||
|
->whereNull('map_lahan.deleted_at')
|
||||||
|
->whereIn('map_lahan.petani_id', clone $petaniIds)
|
||||||
|
->select('petani.komoditas_id')
|
||||||
|
->selectRaw('SUM(map_lahan.luas_lahan) as total_luas')
|
||||||
|
->groupBy('petani.komoditas_id')
|
||||||
|
->pluck('total_luas', 'komoditas_id');
|
||||||
|
|
||||||
|
$productionByCommodity = DB::table('survey_panen')
|
||||||
|
->join('petani', 'petani.id', '=', 'survey_panen.petani_id')
|
||||||
|
->whereNull('survey_panen.deleted_at')
|
||||||
|
->whereIn('survey_panen.petani_id', clone $petaniIds)
|
||||||
|
->select('petani.komoditas_id')
|
||||||
|
->selectRaw('SUM(survey_panen.hasil_komoditas_utama_permusim) as total_produksi_kg')
|
||||||
|
->groupBy('petani.komoditas_id')
|
||||||
|
->pluck('total_produksi_kg', 'komoditas_id');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => $selectedCommodities->pluck('nama')->values()->all(),
|
||||||
|
'series' => $selectedCommodities
|
||||||
|
->map(function ($item) use ($landAreaByCommodity, $productionByCommodity) {
|
||||||
|
$commodityId = (int) $item->id;
|
||||||
|
$totalLandArea = (float) ($landAreaByCommodity[$commodityId] ?? 0);
|
||||||
|
$totalProductionKg = (float) ($productionByCommodity[$commodityId] ?? 0);
|
||||||
|
|
||||||
|
return $totalLandArea > 0
|
||||||
|
? round($totalProductionKg / $totalLandArea, 2)
|
||||||
|
: 0;
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function toKilograms(float $value, string $unit): float
|
private function toKilograms(float $value, string $unit): float
|
||||||
{
|
{
|
||||||
return match (strtolower($unit)) {
|
return match (strtolower($unit)) {
|
||||||
@@ -402,6 +959,64 @@ class SummaryController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ageBucketByCommodityChart($rows, $lahan, string $field): array
|
||||||
|
{
|
||||||
|
$order = ['< 3 tahun', '3-7 tahun', '8-15 tahun', '> 15 tahun'];
|
||||||
|
$lahanKomoditas = $lahan
|
||||||
|
->mapWithKeys(fn ($row) => [(int) $row->id => $row->komoditas_nama ?: 'Belum ditentukan']);
|
||||||
|
|
||||||
|
$komoditasNames = DB::table('master_komoditas')
|
||||||
|
->whereNull('deleted_at')
|
||||||
|
->pluck('nama', 'id');
|
||||||
|
|
||||||
|
$matrix = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$age = $row->{$field} ?? null;
|
||||||
|
|
||||||
|
if ($age === null || $age === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$commodity = 'Belum ditentukan';
|
||||||
|
|
||||||
|
if (! empty($row->komoditas_id) && $komoditasNames->has($row->komoditas_id)) {
|
||||||
|
$commodity = $komoditasNames[$row->komoditas_id];
|
||||||
|
} elseif (! empty($row->lahan_id) && $lahanKomoditas->has((int) $row->lahan_id)) {
|
||||||
|
$commodity = $lahanKomoditas[(int) $row->lahan_id];
|
||||||
|
} elseif (! empty($row->komoditas_nama)) {
|
||||||
|
$commodity = $row->komoditas_nama;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bucket = $this->ageBucketLabel((float) $age);
|
||||||
|
$matrix[$commodity] ??= array_fill_keys($order, 0);
|
||||||
|
$matrix[$commodity][$bucket]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($matrix, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => array_keys($matrix),
|
||||||
|
'series' => collect($order)
|
||||||
|
->map(fn ($bucket) => [
|
||||||
|
'name' => $bucket,
|
||||||
|
'data' => collect($matrix)->map(fn ($counts) => (int) ($counts[$bucket] ?? 0))->values()->all(),
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ageBucketLabel(float $age): string
|
||||||
|
{
|
||||||
|
return match (true) {
|
||||||
|
$age < 3 => '< 3 tahun',
|
||||||
|
$age <= 7 => '3-7 tahun',
|
||||||
|
$age <= 15 => '8-15 tahun',
|
||||||
|
default => '> 15 tahun',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private function attributeChart($rows, string $field, callable $labeler): array
|
private function attributeChart($rows, string $field, callable $labeler): array
|
||||||
{
|
{
|
||||||
$counts = $rows
|
$counts = $rows
|
||||||
@@ -434,6 +1049,40 @@ class SummaryController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function enumOptionByCommodityChart($rows, string $field, string $enumClass): array
|
||||||
|
{
|
||||||
|
$options = collect($enumClass::options())
|
||||||
|
->mapWithKeys(fn ($option) => [(string) $option['value'] => $option['label']]);
|
||||||
|
$matrix = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$value = $row->{$field} ?? null;
|
||||||
|
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$commodity = $row->komoditas_nama ?: 'Belum ditentukan';
|
||||||
|
$matrix[$commodity] ??= $options->keys()
|
||||||
|
->mapWithKeys(fn ($optionValue) => [(string) $optionValue => 0])
|
||||||
|
->all();
|
||||||
|
$matrix[$commodity][(string) $value] = (int) ($matrix[$commodity][(string) $value] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($matrix, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => array_keys($matrix),
|
||||||
|
'series' => $options
|
||||||
|
->map(fn ($label, $value) => [
|
||||||
|
'name' => $label,
|
||||||
|
'data' => collect($matrix)->map(fn ($counts) => (int) ($counts[(string) $value] ?? 0))->values()->all(),
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function arrayOptionChart($rows, string $field, array $labels, ?callable $normalizer = null): array
|
private function arrayOptionChart($rows, string $field, array $labels, ?callable $normalizer = null): array
|
||||||
{
|
{
|
||||||
$counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]);
|
$counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]);
|
||||||
@@ -459,6 +1108,100 @@ class SummaryController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function arrayOptionByCommodityChart($rows, string $field, array $labels, ?callable $normalizer = null): array
|
||||||
|
{
|
||||||
|
$matrix = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$commodity = $row->komoditas_nama ?: 'Belum ditentukan';
|
||||||
|
|
||||||
|
foreach ($this->arrayValues($row->{$field} ?? null) as $value) {
|
||||||
|
$label = $normalizer ? $normalizer($value) : $value;
|
||||||
|
|
||||||
|
if ($label === null || $label === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array($label, $labels, true)) {
|
||||||
|
$labels[] = $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matrix[$commodity] ??= collect($labels)
|
||||||
|
->mapWithKeys(fn ($optionLabel) => [$optionLabel => 0])
|
||||||
|
->all();
|
||||||
|
$matrix[$commodity][$label] = (int) ($matrix[$commodity][$label] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($matrix, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => array_keys($matrix),
|
||||||
|
'series' => collect($labels)
|
||||||
|
->map(fn ($label) => [
|
||||||
|
'name' => $label,
|
||||||
|
'data' => collect($matrix)->map(fn ($counts) => (int) ($counts[$label] ?? 0))->values()->all(),
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fermentationPracticeChart($rows): array
|
||||||
|
{
|
||||||
|
$labels = ['Ya, rutin', 'Pernah mencoba', 'Belum pernah'];
|
||||||
|
$counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]);
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$label = $this->fermentationPracticeLabel($row->praktik_fermentasi ?? null);
|
||||||
|
|
||||||
|
if ($label === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$counts[$label] = (int) $counts[$label] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => $labels,
|
||||||
|
'series' => collect($labels)->map(fn ($label) => (int) ($counts[$label] ?? 0))->values()->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fermentationPracticeLabel(?string $value): ?string
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
in_array($value, [
|
||||||
|
PraktikFermentasi::RUTIN_SENDIRI->value,
|
||||||
|
PraktikFermentasi::RUTIN_KELOMPOK->value,
|
||||||
|
], true) || str_starts_with($value, 'Ya, rutin') => 'Ya, rutin',
|
||||||
|
in_array($value, [
|
||||||
|
PraktikFermentasi::PERNAH_SENDIRI->value,
|
||||||
|
PraktikFermentasi::PERNAH_KELOMPOK->value,
|
||||||
|
], true) || str_starts_with($value, 'Pernah mencoba') => 'Pernah mencoba',
|
||||||
|
$value === PraktikFermentasi::BELUM_PERNAH->value || str_starts_with($value, 'Belum') => 'Belum pernah',
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function surveyProductivityByUnitChart($rows, string $unit): array
|
||||||
|
{
|
||||||
|
$averages = $rows
|
||||||
|
->filter(fn ($row) => $row->satuan === $unit && (float) ($row->kuantitas ?? 0) > 0)
|
||||||
|
->groupBy(fn ($row) => $row->komoditas_nama ?: 'Belum ditentukan')
|
||||||
|
->map(fn ($items) => round((float) $items->avg(fn ($row) => (float) $row->kuantitas), 2))
|
||||||
|
->sortDesc();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'labels' => $averages->keys()->values()->all(),
|
||||||
|
'series' => $averages->values()->all(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function symptomChart($rows): array
|
private function symptomChart($rows): array
|
||||||
{
|
{
|
||||||
$counts = collect();
|
$counts = collect();
|
||||||
@@ -515,7 +1258,12 @@ class SummaryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (method_exists($enumClass, 'tryFrom')) {
|
if (method_exists($enumClass, 'tryFrom')) {
|
||||||
$case = $enumClass::tryFrom($rawValue);
|
$cases = $enumClass::cases();
|
||||||
|
$enumValue = isset($cases[0]) && is_int($cases[0]->value) && is_numeric($rawValue)
|
||||||
|
? (int) $rawValue
|
||||||
|
: $rawValue;
|
||||||
|
$case = $enumClass::tryFrom($enumValue);
|
||||||
|
|
||||||
if ($case && method_exists($case, 'label')) {
|
if ($case && method_exists($case, 'label')) {
|
||||||
$label = $case->label();
|
$label = $case->label();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
@extends('layout.main')
|
||||||
|
|
||||||
|
@push('style')
|
||||||
|
<style>
|
||||||
|
.pasar-filter-card .filter-item {
|
||||||
|
min-width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasar-kpi-value {
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
font-size: 1.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasar-kpi-label {
|
||||||
|
color: var(--bs-heading-color);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasar-kpi-desc {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasar-empty-state {
|
||||||
|
min-height: 220px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
.pasar-filter-card .filter-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<form id="form-filter" class="col-12" method="GET" action="{{ route('dashboard.pasar') }}">
|
||||||
|
<div class="card card-border-shadow-primary pasar-filter-card">
|
||||||
|
<div class="card-body py-3">
|
||||||
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<div class="d-flex align-items-center me-2">
|
||||||
|
<i class="icon-base ti tabler-adjustments-horizontal me-2"></i>
|
||||||
|
<span class="fw-medium">Komoditas <span class="text-danger">*</span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<select id="filter-komoditas" class="form-select form-select-sm" name="komoditas_id">
|
||||||
|
<option value="">Pilih Komoditas</option>
|
||||||
|
@foreach ($komoditas as $item)
|
||||||
|
<option value="{{ $item->id }}" {{ old('komoditas_id', $komoditas_id) == $item->id ? 'selected' : '' }}>
|
||||||
|
{{ $item->nama }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="icon-base ti tabler-filter me-1"></i>
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
|
<button type="button" id="btn-reset-dashboard-filter" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="icon-base ti tabler-refresh me-1"></i>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-3">
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<div class="d-flex align-items-center me-2">
|
||||||
|
<i class="icon-base ti tabler-map-2 me-2"></i>
|
||||||
|
<span class="fw-medium">Wilayah</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<select id="filter-provinsi" class="form-select form-select-sm" name="provinsi_id" data-default="{{ $provinsi_id }}">
|
||||||
|
<option value="">Semua Provinsi</option>
|
||||||
|
@foreach($provinsi as $p)
|
||||||
|
<option value="{{ $p->provinsi_id }}" {{ old('provinsi_id', $provinsi_id) == $p->provinsi_id ? 'selected' : '' }}>
|
||||||
|
{{ $p->provinsi }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<select id="filter-kabkota" class="form-select form-select-sm" name="kabupatenKota_id" data-default="{{ $kabupatenKota_id }}" disabled>
|
||||||
|
<option value="">Semua Kabupaten/Kota</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<select id="filter-kecamatan" class="form-select form-select-sm" name="kecamatan_id" data-default="{{ $kecamatan_id }}" disabled>
|
||||||
|
<option value="">Semua Kecamatan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-item">
|
||||||
|
<select id="filter-desa" class="form-select form-select-sm" name="desaKelurahan_id" data-default="{{ $desaKelurahan_id }}" disabled>
|
||||||
|
<option value="">Semua Desa/Kelurahan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (! $selectedKomoditas)
|
||||||
|
<div class="alert alert-primary d-flex align-items-start gap-3" role="alert">
|
||||||
|
<i class="icon-base ti tabler-info-circle mt-1"></i>
|
||||||
|
<div>
|
||||||
|
<h6 class="alert-heading mb-1">Pilih komoditas terlebih dahulu</h6>
|
||||||
|
<div>Data dashboard pasar baru akan ditampilkan setelah filter komoditas wajib diisi.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body pasar-empty-state">
|
||||||
|
<div>
|
||||||
|
<div class="avatar avatar-lg mx-auto mb-3">
|
||||||
|
<span class="avatar-initial rounded bg-label-primary">
|
||||||
|
<i class="icon-base ti tabler-building-store icon-28px"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-1">Belum ada data pasar yang ditampilkan</h5>
|
||||||
|
<p class="text-body-secondary mb-0">Gunakan filter komoditas untuk melihat harga jual rata-rata.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<div class="col-xl-4 col-md-6">
|
||||||
|
<div class="card card-border-shadow-{{ $priceCard['variant'] }} h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center gap-4">
|
||||||
|
<div class="avatar avatar-lg">
|
||||||
|
<span class="avatar-initial rounded bg-label-{{ $priceCard['variant'] }}">
|
||||||
|
<i class="icon-base ti {{ $priceCard['icon'] }} icon-28px"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-baseline gap-2 flex-wrap">
|
||||||
|
<h4 class="mb-0 pasar-kpi-value">{{ $priceCard['value'] }}</h4>
|
||||||
|
<small class="text-body-secondary">{{ $priceCard['unit'] }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mb-0 mt-1 pasar-kpi-label">{{ $priceCard['label'] }}</p>
|
||||||
|
<small class="text-body-secondary pasar-kpi-desc">{{ $priceCard['description'] }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 pt-3 border-top d-flex align-items-center justify-content-between gap-3">
|
||||||
|
<span class="badge bg-label-success">{{ $priceCard['caption'] }}</span>
|
||||||
|
<small class="text-body-secondary">{{ number_format($priceCard['records'], 0, ',', '.') }} data harga</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const wilayah = @json($wilayah);
|
||||||
|
|
||||||
|
function fillSelect($select, items, placeholder) {
|
||||||
|
$select.empty().append(new Option(placeholder, ''));
|
||||||
|
items.forEach(function (item) {
|
||||||
|
$select.append(new Option(item.nama, item.id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedProvinsi() {
|
||||||
|
const id = $('#filter-provinsi').val();
|
||||||
|
return wilayah.find(function (item) {
|
||||||
|
return String(item.id) === String(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedKabupaten() {
|
||||||
|
const provinsi = selectedProvinsi();
|
||||||
|
const id = $('#filter-kabkota').val();
|
||||||
|
return (provinsi?.kabupaten || []).find(function (item) {
|
||||||
|
return String(item.id) === String(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedKecamatan() {
|
||||||
|
const kabupaten = selectedKabupaten();
|
||||||
|
const id = $('#filter-kecamatan').val();
|
||||||
|
return (kabupaten?.kecamatan || []).find(function (item) {
|
||||||
|
return String(item.id) === String(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#filter-provinsi, #filter-kabkota, #filter-kecamatan, #filter-desa, #filter-komoditas').select2({ width: '100%' });
|
||||||
|
|
||||||
|
$('#filter-provinsi').on('change', function () {
|
||||||
|
const provinsi = selectedProvinsi();
|
||||||
|
fillSelect($('#filter-kabkota'), provinsi?.kabupaten || [], 'Semua Kabupaten/Kota');
|
||||||
|
fillSelect($('#filter-kecamatan'), [], 'Semua Kecamatan');
|
||||||
|
fillSelect($('#filter-desa'), [], 'Semua Desa/Kelurahan');
|
||||||
|
$('#filter-kabkota').prop('disabled', !provinsi).trigger('change.select2');
|
||||||
|
$('#filter-kecamatan, #filter-desa').prop('disabled', true).trigger('change.select2');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#filter-kabkota').on('change', function () {
|
||||||
|
const kabupaten = selectedKabupaten();
|
||||||
|
fillSelect($('#filter-kecamatan'), kabupaten?.kecamatan || [], 'Semua Kecamatan');
|
||||||
|
fillSelect($('#filter-desa'), [], 'Semua Desa/Kelurahan');
|
||||||
|
$('#filter-kecamatan').prop('disabled', !kabupaten).trigger('change.select2');
|
||||||
|
$('#filter-desa').prop('disabled', true).trigger('change.select2');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#filter-kecamatan').on('change', function () {
|
||||||
|
const kecamatan = selectedKecamatan();
|
||||||
|
fillSelect($('#filter-desa'), kecamatan?.desa || [], 'Semua Desa/Kelurahan');
|
||||||
|
$('#filter-desa').prop('disabled', !kecamatan).trigger('change.select2');
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultProvinsi = String($('#filter-provinsi').data('default') || '');
|
||||||
|
const defaultKabupaten = String($('#filter-kabkota').data('default') || '');
|
||||||
|
const defaultKecamatan = String($('#filter-kecamatan').data('default') || '');
|
||||||
|
const defaultDesa = String($('#filter-desa').data('default') || '');
|
||||||
|
|
||||||
|
if (defaultProvinsi) {
|
||||||
|
$('#filter-provinsi').val(defaultProvinsi).trigger('change');
|
||||||
|
if (defaultKabupaten) {
|
||||||
|
$('#filter-kabkota').val(defaultKabupaten).trigger('change');
|
||||||
|
if (defaultKecamatan) {
|
||||||
|
$('#filter-kecamatan').val(defaultKecamatan).trigger('change');
|
||||||
|
if (defaultDesa) {
|
||||||
|
$('#filter-desa').val(defaultDesa).trigger('change.select2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btn-reset-dashboard-filter').on('click', function () {
|
||||||
|
window.location.href = @json(route('dashboard.pasar'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul class="menu-sub">
|
||||||
|
<li class="menu-item {{ request()->routeIs('dashboard.pasar') ? 'active' : '' }}">
|
||||||
|
<a href="{{ route('dashboard.pasar') }}" class="menu-link">
|
||||||
|
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-building-store"></i></span><div data-i18n="Pasar">Pasar</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<ul class="menu-sub">
|
<ul class="menu-sub">
|
||||||
<li class="menu-item {{ request()->routeIs('dashboard.indikator-pertanian') ? 'active' : '' }}">
|
<li class="menu-item {{ request()->routeIs('dashboard.indikator-pertanian') ? 'active' : '' }}">
|
||||||
<a href="{{ route('dashboard.indikator-pertanian') }}" class="menu-link">
|
<a href="{{ route('dashboard.indikator-pertanian') }}" class="menu-link">
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Route::middleware('auth')
|
|||||||
// Home
|
// Home
|
||||||
|
|
||||||
Route::get('/dashboard/produktivitas', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'produktivitas'])->name('dashboard.produktivitas');
|
Route::get('/dashboard/produktivitas', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'produktivitas'])->name('dashboard.produktivitas');
|
||||||
|
Route::get('/dashboard/pasar', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'pasar'])->name('dashboard.pasar');
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
Route::get('/dashboard/petani', [App\Http\Controllers\Web\Dashboard\ProfilPetaniController::class, 'index'])->name('dashboard.petani');
|
Route::get('/dashboard/petani', [App\Http\Controllers\Web\Dashboard\ProfilPetaniController::class, 'index'])->name('dashboard.petani');
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Web\Dashboard\SummaryController;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Schema::create('master_komoditas', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('nama');
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
Schema::create('master_kabupaten_kota', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('provinsi_id');
|
||||||
|
});
|
||||||
|
Schema::create('master_kecamatan', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('kabupaten_kota_id');
|
||||||
|
});
|
||||||
|
Schema::create('master_desa_kelurahan', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('kecamatan_id');
|
||||||
|
});
|
||||||
|
Schema::create('petani', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('komoditas_id');
|
||||||
|
$table->integer('desa_kelurahan_id');
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
Schema::create('map_lahan', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('petani_id');
|
||||||
|
$table->decimal('luas_lahan', 12, 4);
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
Schema::create('survey_panen', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->integer('petani_id');
|
||||||
|
$table->decimal('hasil_komoditas_utama_permusim', 12, 2)->nullable();
|
||||||
|
$table->decimal('hasil_komoditas_lainnya_permusim', 12, 2)->nullable();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::table('master_komoditas')->insert([
|
||||||
|
['id' => 1, 'nama' => 'Kakao'],
|
||||||
|
['id' => 2, 'nama' => 'Vanili'],
|
||||||
|
['id' => 3, 'nama' => 'Kemiri'],
|
||||||
|
['id' => 4, 'nama' => 'Kopi'],
|
||||||
|
]);
|
||||||
|
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'provinsi_id' => 1]);
|
||||||
|
DB::table('master_kecamatan')->insert(['id' => 1, 'kabupaten_kota_id' => 1]);
|
||||||
|
DB::table('master_desa_kelurahan')->insert([
|
||||||
|
['id' => 1, 'kecamatan_id' => 1],
|
||||||
|
['id' => 2, 'kecamatan_id' => 1],
|
||||||
|
]);
|
||||||
|
DB::table('petani')->insert([
|
||||||
|
['id' => 1, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||||
|
['id' => 2, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||||
|
['id' => 3, 'komoditas_id' => 4, 'desa_kelurahan_id' => 2, 'deleted_at' => null],
|
||||||
|
['id' => 4, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => '2026-01-01'],
|
||||||
|
['id' => 5, 'komoditas_id' => 2, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||||
|
]);
|
||||||
|
DB::table('map_lahan')->insert([
|
||||||
|
['petani_id' => 1, 'luas_lahan' => 0.5, 'deleted_at' => null],
|
||||||
|
['petani_id' => 1, 'luas_lahan' => 1.5, 'deleted_at' => null],
|
||||||
|
['petani_id' => 2, 'luas_lahan' => 3, 'deleted_at' => null],
|
||||||
|
['petani_id' => 3, 'luas_lahan' => 2, 'deleted_at' => null],
|
||||||
|
['petani_id' => 4, 'luas_lahan' => 50, 'deleted_at' => null],
|
||||||
|
['petani_id' => 1, 'luas_lahan' => 100, 'deleted_at' => '2026-01-01'],
|
||||||
|
['petani_id' => 5, 'luas_lahan' => 0, 'deleted_at' => null],
|
||||||
|
]);
|
||||||
|
DB::table('survey_panen')->insert([
|
||||||
|
['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 300, 'deleted_at' => null],
|
||||||
|
['petani_id' => 2, 'hasil_komoditas_utama_permusim' => 600, 'deleted_at' => null],
|
||||||
|
['petani_id' => 3, 'hasil_komoditas_utama_permusim' => 400, 'deleted_at' => null],
|
||||||
|
['petani_id' => 4, 'hasil_komoditas_utama_permusim' => 10000, 'deleted_at' => null],
|
||||||
|
['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 20000, 'deleted_at' => '2026-01-01'],
|
||||||
|
['petani_id' => 5, 'hasil_komoditas_utama_permusim' => 10, 'deleted_at' => null],
|
||||||
|
]);
|
||||||
|
DB::table('survey_panen')->where('petani_id', 1)->update(['hasil_komoditas_lainnya_permusim' => 500]);
|
||||||
|
|
||||||
|
$controller = app(SummaryController::class);
|
||||||
|
$method = new ReflectionMethod($controller, 'actualProductivityByCommodityChart');
|
||||||
|
$this->chart = fn (array $filters = []) => $method->invoke($controller, new Request($filters));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('actual productivity uses seasonal survey harvest and counts each plot once', function () {
|
||||||
|
$chart = ($this->chart)();
|
||||||
|
|
||||||
|
expect($chart['labels'])->toBe(['Kakao', 'Vanili', 'Kemiri', 'Kopi']);
|
||||||
|
expect($chart['series'])->toEqual([180, 0, 0, 200]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('actual productivity respects region and commodity filters', function () {
|
||||||
|
expect(($this->chart)(['desaKelurahan_id' => 1])['series'])->toEqual([180, 0, 0, 0]);
|
||||||
|
expect(($this->chart)(['desaKelurahan_id' => 2, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 200]);
|
||||||
|
expect(($this->chart)(['desaKelurahan_id' => 1, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 0]);
|
||||||
|
expect(($this->chart)(['provinsi_id' => 99])['series'])->toEqual([0, 0, 0, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('actual productivity rounds kg per hectare to two decimals', function () {
|
||||||
|
DB::table('map_lahan')->where('petani_id', 5)->update(['luas_lahan' => 0.3]);
|
||||||
|
|
||||||
|
expect(($this->chart)()['series'][1])->toBe(33.33);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user