diff --git a/app/Http/Controllers/Web/Dashboard/SummaryController.php b/app/Http/Controllers/Web/Dashboard/SummaryController.php index f581fd2..437d0d6 100644 --- a/app/Http/Controllers/Web/Dashboard/SummaryController.php +++ b/app/Http/Controllers/Web/Dashboard/SummaryController.php @@ -223,7 +223,6 @@ class SummaryController extends Controller $healthSourceName = $tanaman->isNotEmpty() ? 'tanaman' : 'lahan'; $landMetrics = $this->karakteristikLandMetrics($request); - $totalPetani = $landMetrics['total_petani']; $totalLuasLahan = $landMetrics['total_luas_lahan']; $avgLuasPerPetani = $landMetrics['ratarata_luas_lahan']; $actualProductivity = $this->actualProductivityByCommodityChart($request); @@ -233,14 +232,6 @@ class SummaryController extends Controller $aksesPasar = $this->filteredAksesPasar($request); $kpiCards = [ - [ - 'value' => number_format($totalPetani, 0, ',', '.'), - 'unit' => 'orang', - 'label' => 'Jumlah Petani', - 'description' => 'Petani sesuai filter yang dipilih', - 'icon' => 'tabler-users', - 'variant' => 'primary', - ], [ 'value' => number_format($totalLuasLahan, 2, ',', '.'), 'unit' => 'ha', @@ -294,7 +285,7 @@ class SummaryController extends Controller : $value, ), 'praktikSortasiPanen' => $this->enumOptionChart($surveyPanen, 'praktik_sortasi', PraktikSortasi::class), - 'praktikFermentasiPascapanen' => $this->fermentationPracticeChart($aksesPasar), + 'praktikFermentasiPascapanen' => $this->fermentationPracticeByCommodityChart($aksesPasar), 'produktivitasAktualPerKomoditas' => $actualProductivity, ], ]; @@ -312,7 +303,6 @@ class SummaryController extends Controller 'desa' => (string) $request->input('desaKelurahan_id', ''), ], 'levels' => $levels, - 'cultivationLevels' => $this->productionCultivationMapLevelData($request, $levels), 'information' => $this->productionMapInformationData($request), ]; } @@ -511,6 +501,7 @@ class SummaryController extends Controller 'map_lahan.path', 'map_lahan.luas_lahan', 'map_lahan.jumlah_tanaman', + 'map_lahan.petani_id', 'map_lahan.umur_tanaman', 'map_lahan.serangan_hama', 'map_lahan.penyakit_tanaman', @@ -537,58 +528,10 @@ class SummaryController extends Controller ]) ->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')) + ->whereIn('praktik_budidaya.petani_id', $petaniIds) ->orderByDesc('praktik_budidaya.updated_at') ->get([ 'praktik_budidaya.petani_id', @@ -601,7 +544,10 @@ class SummaryController extends Controller $geoLahan = $mapLahan ->filter(fn ($lahan) => $this->hasMapPolygon($lahan->path)) - ->map(function ($lahan) use ($practiceByPetani) { + ->map(function ($lahan) use ($tanamanByLahan, $practiceByPetani) { + $tanamanRows = $tanamanByLahan->get($lahan->id, collect()); + $komoditas = $this->productionCommodityDetails($lahan, $tanamanRows); + $opt = $this->productionOptDetails($lahan, $tanamanRows); $practice = $practiceByPetani->get($lahan->petani_id); return [ @@ -610,6 +556,8 @@ class SummaryController extends Controller 'polygon' => $lahan->path, 'nama_petani' => $lahan->petani_nama ?? $lahan->profile_nama ?? 'N/A', 'luas' => (float) $lahan->luas_lahan, + 'komoditas' => $komoditas, + 'opt' => $opt, '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), @@ -619,10 +567,10 @@ class SummaryController extends Controller ->all(); return [ - 'provinsi' => $baseLevels['provinsi'] ?? [], - 'kabupaten' => $baseLevels['kabupaten'] ?? [], - 'kecamatan' => $baseLevels['kecamatan'] ?? [], - 'desa' => $baseLevels['desa'] ?? [], + 'provinsi' => array_values($geoProvinsi), + 'kabupaten' => array_values($geoKabupaten), + 'kecamatan' => array_values($geoKecamatan), + 'desa' => array_values($geoDesa), 'lahan' => $geoLahan, ]; } @@ -826,7 +774,12 @@ class SummaryController extends Controller return DB::table('akses_pasar') ->whereNull('akses_pasar.deleted_at') ->whereIn('akses_pasar.petani_id', $petaniIds) - ->get(['praktik_fermentasi']); + ->leftJoin('petani as akses_petani', 'akses_petani.id', '=', 'akses_pasar.petani_id') + ->leftJoin('master_komoditas as akses_komoditas', 'akses_komoditas.id', '=', 'akses_petani.komoditas_id') + ->get([ + 'akses_pasar.praktik_fermentasi', + 'akses_komoditas.nama as komoditas_nama', + ]); } private function filteredSurveyProduktivitas(Request $request) @@ -1254,10 +1207,10 @@ class SummaryController extends Controller ]; } - private function fermentationPracticeChart($rows): array + private function fermentationPracticeByCommodityChart($rows): array { $labels = ['Ya, rutin', 'Pernah mencoba', 'Belum pernah']; - $counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]); + $matrix = []; foreach ($rows as $row) { $label = $this->fermentationPracticeLabel($row->praktik_fermentasi ?? null); @@ -1266,12 +1219,21 @@ class SummaryController extends Controller continue; } - $counts[$label] = (int) $counts[$label] + 1; + $commodity = $row->komoditas_nama ?: 'Belum ditentukan'; + $matrix[$commodity] ??= array_fill_keys($labels, 0); + $matrix[$commodity][$label]++; } + ksort($matrix, SORT_NATURAL | SORT_FLAG_CASE); + return [ - 'labels' => $labels, - 'series' => collect($labels)->map(fn ($label) => (int) ($counts[$label] ?? 0))->values()->all(), + 'labels' => array_keys($matrix), + 'series' => collect($labels) + ->map(fn ($label) => [ + 'name' => $label, + 'data' => collect($matrix)->map(fn ($counts) => (int) $counts[$label])->values()->all(), + ]) + ->all(), ]; } diff --git a/app/Http/Controllers/Web/Produktivitas/ProfilPetaniController.php b/app/Http/Controllers/Web/Produktivitas/ProfilPetaniController.php index 791fba0..15df567 100644 --- a/app/Http/Controllers/Web/Produktivitas/ProfilPetaniController.php +++ b/app/Http/Controllers/Web/Produktivitas/ProfilPetaniController.php @@ -49,20 +49,43 @@ class ProfilPetaniController extends Controller $skorPetani = SkorPetani::where('petani_id', $id)->first(); $enumerator = Profile::where('user_id', $petani->created_by)->first(); $komoditasUtama = Komoditas::where('id', $petani->komoditas_id)->first(); - $lahan = Lahan::where('petani_id', $id )->get(); + $lahan = Lahan::query() + ->select(['id', 'petani_id', 'nama', 'path', 'luas_lahan', 'komoditas_id', 'desa_kelurahan_id']) + ->with([ + 'komoditas:id,nama', + 'desaKelurahan:id,nama,path,kecamatan_id', + 'desaKelurahan.kecamatan:id,nama,path', + ]) + ->where('petani_id', $id) + ->get(); $desaKelurahan = DesaKelurahan::with('kecamatan') ->where('id', $petani->desa_kelurahan_id) ->first(); $ktpMasked = $petani->ktp ? Str::mask($petani->ktp, '*', 4, -4) : '-'; - $profiling = $skorPetani ? $this->buildProfiling($skorPetani) : null; + $assessmentFields = [ + 'skor_kesiapan_adopsi', + 'skor_d2_sistem_praktik_budidaya', + 'skor_d4_sosial_ekonomi', + 'skor_d6_rantai_nilai_akses_pasar', + 'skor_komp_1_d4_k3_motivasi_kapasitas_adopsi', + 'skor_komp_2_d4_k2_ekonomi_rumah_tangga', + 'skor_komp_3_d2_praktik_budidaya_gap', + 'skor_komp_4_d4_k4_keterlibatan_kelembagaan', + 'skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan', + 'skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar', + ]; + $hasAssessment = $skorPetani !== null && collect($assessmentFields)->every( + fn (string $field) => $skorPetani->{$field} !== null + ); + $profiling = $hasAssessment ? $this->buildProfiling($skorPetani) : null; $tanaman = Tanaman::where('petani_id', $petani->id) ->where('komoditas_id', $petani->komoditas_id) ->first(); - if ($tanaman) { + if ($tanaman?->updated_at && $tanaman->umur_tanaman !== null) { $tahunInputTanaman = $tanaman->updated_at->year; $umurTanaman = (int) $tanaman->umur_tanaman; $tahunSekarang = now()->year; @@ -162,8 +185,8 @@ class ProfilPetaniController extends Controller $d4SosialEkonomi = null; - $terdaftarKelompok = $sosialEkonomi->terdaftar_kelompok_tani; - $peranKelompok = $sosialEkonomi->peran_dalam_kelompok; + $terdaftarKelompok = $sosialEkonomi?->terdaftar_kelompok_tani; + $peranKelompok = $sosialEkonomi?->peran_dalam_kelompok; if ($sosialEkonomi) { @@ -207,8 +230,8 @@ class ProfilPetaniController extends Controller 'icon' => 'tabler-minus', ], default => [ - 'label' => Str::headline($status), - 'short' => Str::headline($status), + 'label' => filled($status) ? Str::headline($status) : 'Belum diisi', + 'short' => filled($status) ? Str::headline($status) : 'Belum diisi', 'color' => 'secondary', 'icon' => 'tabler-help-circle', ], @@ -434,12 +457,61 @@ class ProfilPetaniController extends Controller ], ]; + // Report missing profile data without treating an unanswered field as zero. + $missingSections = []; + $identityFields = [ + 'nama' => 'Nama', 'ktp' => 'NIK', 'tanggal_lahir' => 'Tanggal lahir', + 'jenis_kelamin' => 'Jenis kelamin', 'pendidikan_terakhir' => 'Pendidikan terakhir', + 'status_perkawinan' => 'Status perkawinan', + ]; + foreach ($identityFields as $field => $label) { + if (blank($petani->{$field})) { + $missingSections[] = $label; + } + } + if (!$desaKelurahan?->nama || !$desaKelurahan?->kecamatan?->nama) { + $missingSections[] = 'Wilayah desa/kecamatan'; + } + if (!$komoditasUtama?->nama) { + $missingSections[] = 'Komoditas utama'; + } + if ($lahan->isEmpty()) { + $missingSections[] = 'Data lahan'; + } + if ($tahunTanam === null) { + $missingSections[] = 'Umur tanaman'; + } + if (!$praktikBudidaya || $indikatorOnFarm->contains(fn ($item) => $item['skor'] === null)) { + $missingSections[] = 'Praktik budidaya'; + } + if ($surveyPanen?->hasil_komoditas_utama_permusim === null) { + $missingSections[] = 'Survei panen'; + } + $socialFields = [ + 'jumlah_tanggungan', 'skor_kecukupan_penghasilan', 'skor_kemampuan_menabung', + 'status_kepemilikan_lahan', 'terdaftar_kelompok_tani', 'status_rekening_tabungan', + 'pengalaman_kredit_kebun', 'keterikatan_hutang_panen', 'praktik_ijon', + 'kebebasan_memilih_pembeli', + ]; + if (!$sosialEkonomi || collect($socialFields)->contains(fn ($field) => blank($sosialEkonomi->{$field}))) { + $missingSections[] = 'Sosial ekonomi'; + } + if (!$aksesPasar || blank($aksesPasar->saluran_terdahulu)) { + $missingSections[] = 'Akses pasar'; + } + if (!$hasAssessment) { + $missingSections[] = 'Penilaian kesiapan adopsi belum lengkap'; + } + $data = [ + 'missingSections' => $missingSections, + 'hasAssessment' => $hasAssessment, 'petani' => $petani, 'skorPetani' => $skorPetani, 'enumerator' => $enumerator, 'komoditasUtama' => $komoditasUtama, 'lahan' => $lahan, + 'farmerLandMap' => $this->farmerLandMapData($lahan), 'desaKelurahan' => $desaKelurahan, 'ktpMasked' => $ktpMasked, 'profiling' => $profiling, @@ -447,7 +519,6 @@ class ProfilPetaniController extends Controller 'umurTanaman' => $umurTanaman, 'surveyPanen' => $surveyPanen, 'praktikBudidaya' => $praktikBudidaya, - 'praktikBudidaya' => $praktikBudidaya, 'indikatorOnFarm' => $indikatorOnFarm, 'detailOnFarm' => $detailOnFarm, 'sosialEkonomi' => $sosialEkonomi, @@ -464,6 +535,35 @@ class ProfilPetaniController extends Controller return view('produktivitas.profil-petani.index', $data); } + private function farmerLandMapData(\Illuminate\Support\Collection $lands): array + { + $regions = ['desa' => [], 'kecamatan' => []]; + $plots = []; + + foreach ($lands as $land) { + $plot = [ + 'id' => $land->id, + 'nama' => $land->nama, + 'path' => $land->path, + 'luas' => $land->luas_lahan, + 'komoditas' => $land->komoditas?->nama, + ]; + foreach (['desa' => $land->desaKelurahan, 'kecamatan' => $land->desaKelurahan?->kecamatan] as $level => $region) { + $plot[$level . '_id'] = $region?->id; + if ($region && !isset($regions[$level][$region->id])) { + $regions[$level][$region->id] = [ + 'id' => $region->id, + 'nama' => $region->nama, + 'path' => $region->path, + ]; + } + } + $plots[] = $plot; + } + + return ['lands' => $plots, 'regions' => $regions]; + } + private function getScoreColor(?int $score): string { if ($score === null) { diff --git a/public/assets/js/config.js b/public/assets/js/config.js index 128fc68..a968156 100644 --- a/public/assets/js/config.js +++ b/public/assets/js/config.js @@ -81,6 +81,29 @@ window.config = { enableMenuLocalStorage: true // Enable menu state with local storage support }; +// Choose the label color with the higher contrast against each chart slice. +window.config.chartTextColor = function (color) { + let hex = String(color || '').trim().replace(/^#/, ''); + if (/^[0-9a-f]{3}$/i.test(hex)) { + hex = hex.split('').map(value => value + value).join(''); + } + if (!/^[0-9a-f]{6}$/i.test(hex)) return '#212529'; + + const luminance = function (value) { + const channels = value.match(/../g).map(channel => { + const srgb = parseInt(channel, 16) / 255; + return srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4); + }); + return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722; + }; + const background = luminance(hex); + const dark = luminance('212529'); + const darkContrast = (Math.max(background, dark) + 0.05) / (Math.min(background, dark) + 0.05); + const lightContrast = 1.05 / (background + 0.05); + + return darkContrast >= lightContrast ? '#212529' : '#ffffff'; +}; + window.assetsPath = document.documentElement.getAttribute('data-assets-path'); window.templateName = document.documentElement.getAttribute('data-template'); diff --git a/resources/views/dashboard/indikator-pertanian.blade.php b/resources/views/dashboard/indikator-pertanian.blade.php index b4b9b78..e440541 100644 --- a/resources/views/dashboard/indikator-pertanian.blade.php +++ b/resources/views/dashboard/indikator-pertanian.blade.php @@ -2011,7 +2011,11 @@ series: data.series, colors: colors, stroke: { width: 0 }, - dataLabels: { enabled: false }, + dataLabels: { + enabled: false, + style: { colors: colors.map(config.chartTextColor) }, + dropShadow: { enabled: false }, + }, legend: { show: true, position: 'bottom', @@ -2051,7 +2055,7 @@
+
${label}: ${value} petani (${percentage}%)
`; @@ -2433,6 +2418,12 @@ document.addEventListener('DOMContentLoaded', function () { const landOwnershipStatusSeries = (@json($data['status_kepemilikan_lahan']['series'] ?? [])).map(Number); const totalLandOwnershipStatusFarmers = landOwnershipStatusSeries.reduce((total, value) => total + value, 0); + const landOwnershipStatusColors = [ + pastelSemantic.success || '#6FD68F', + pastelSemantic.warning || '#FFB46F', + pastelSemantic.info || '#5FC7D6', + pastelSemantic.secondary || '#9EA6B0' + ]; const landOwnershipStatusChartConfig = { chart: { height: 365, @@ -2452,12 +2443,7 @@ document.addEventListener('DOMContentLoaded', function () { }, labels: landOwnershipStatusLabels, series: landOwnershipStatusSeries, - colors: [ - pastelSemantic.success || '#6FD68F', - pastelSemantic.warning || '#FFB46F', - pastelSemantic.info || '#5FC7D6', - pastelSemantic.secondary || '#9EA6B0' - ], + colors: landOwnershipStatusColors, stroke: { width: 0 }, dataLabels: { enabled: true, @@ -2473,15 +2459,9 @@ document.addEventListener('DOMContentLoaded', function () { fontSize: '13px', fontFamily: fontFamily, fontWeight: 600, - colors: ['#fff'] + colors: landOwnershipStatusColors.map(config.chartTextColor) }, - dropShadow: { - enabled: true, - top: 1, - left: 1, - blur: 2, - opacity: 0.45 - } + dropShadow: { enabled: false } }, legend: { show: true, @@ -2918,6 +2898,8 @@ document.addEventListener('DOMContentLoaded', function () { }, dataLabels: { enabled: false, + style: { colors: segmentPalette.map(config.chartTextColor) }, + dropShadow: { enabled: false }, }, legend: { show: false, diff --git a/resources/views/dashboard/produktivitas.blade.php b/resources/views/dashboard/produktivitas.blade.php index 3a890da..e33ee01 100644 --- a/resources/views/dashboard/produktivitas.blade.php +++ b/resources/views/dashboard/produktivitas.blade.php @@ -758,7 +758,7 @@
Peta Sebaran
-

Lahan, tanaman, dan OPT berdasarkan filter wilayah.

+

Lahan, tanaman, OPT, dan praktik budidaya berdasarkan filter wilayah.

@@ -1044,8 +1016,8 @@
-
Praktik Fermentasi atau Pengolahan Pascapanen
- Apakah sudah ada praktik fermentasi atau pengolahan pascapanen yang dilakukan +
Praktik Fermentasi atau Pengolahan Pascapanen per Komoditas
+ Jumlah petani per komoditas berdasarkan praktik fermentasi atau pengolahan pascapanen.
@@ -1079,11 +1051,7 @@ document.addEventListener('DOMContentLoaded', function () { const mapData = @json($mapData); const dashboardFilters = @json($dashboardFilters); const mapInformationUrl = @json(route('dashboard.produktivitas.map-information')); - const initialMapInformation = mapData.information || {}; - const mapInformation = { - production: { ...initialMapInformation }, - cultivation: { ...initialMapInformation }, - }; + const mapInformation = { ...(mapData.information || {}) }; const wilayah = @json($wilayah); const headingColor = config.colors.headingColor; const legendColor = config.colors.bodyColor; @@ -1094,9 +1062,8 @@ document.addEventListener('DOMContentLoaded', function () { const labelColor = legendColor; const borderColor = pastelChart.grid || 'rgba(128, 128, 128, .18)'; let productionMapRendered = false; - let cultivationMapRendered = false; let actualProductivityRendered = false; - const mapInformationRequestIds = { production: 0, cultivation: 0 }; + let mapInformationRequestId = 0; function hasData(data) { return data && Array.isArray(data.series) && data.series.some(function (value) { @@ -1114,7 +1081,7 @@ document.addEventListener('DOMContentLoaded', function () { }); } - function renderProductionMapInformation(elementId, information, includeOpt = true) { + function renderProductionMapInformation(elementId, information) { const element = document.getElementById(elementId); if (!element) return; @@ -1193,17 +1160,15 @@ document.addEventListener('DOMContentLoaded', function () { Jumlah Petani / Komoditas
${renderBars(commodityRows, 'Belum ada data komoditas.', 'petani')}
- ${includeOpt ? ` -
- OPT (Organisme Pengganggu Tanaman) -
${renderBars(optRows, 'Belum ada data OPT.', 'lahan')}
-
- ` : ''} +
+ OPT (Organisme Pengganggu Tanaman) +
${renderBars(optRows, 'Belum ada data OPT.', 'lahan')}
+
`; } - async function loadProductionMapInformation(mapType, level = null, id = null) { - const requestId = ++mapInformationRequestIds[mapType]; + async function loadProductionMapInformation(level = null, id = null) { + const requestId = ++mapInformationRequestId; const params = new URLSearchParams(); Object.entries(dashboardFilters || {}).forEach(function ([key, value]) { @@ -1225,14 +1190,10 @@ document.addEventListener('DOMContentLoaded', function () { if (!response.ok) return; const information = await response.json(); - if (requestId !== mapInformationRequestIds[mapType]) return; + if (requestId !== mapInformationRequestId) return; - Object.assign(mapInformation[mapType], information); - if (mapType === 'production') { - renderProductionMapInformation('produksi-map-information', mapInformation.production); - } else { - renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false); - } + Object.assign(mapInformation, information); + renderProductionMapInformation('produksi-map-information', mapInformation); } catch (error) { console.error('Gagal memuat informasi wilayah pada peta.', error); } @@ -1290,13 +1251,13 @@ document.addEventListener('DOMContentLoaded', function () { }, 0); } - function produksiChartTooltip(color, label, value, unit = '', total = null, digits = 0) { + function produksiChartTooltip(color, label, value, unit = '', total = null, digits = 0, textColor = headingColor) { const numericValue = Number(value || 0); const unitText = unit ? ` ${escapeHtml(unit)}` : ''; const percentageText = total === null ? '' : ` (${chartPercentage(numericValue, total)}%)`; return ` -
+
${escapeHtml(label)}: ${formatNumber(numericValue, digits)}${unitText}${percentageText}
`; @@ -1425,6 +1386,9 @@ document.addEventListener('DOMContentLoaded', function () { const opt = Array.isArray(item.opt) && item.opt.length ? item.opt.map(value => `${escapeHtml(value)}`).join('') : 'Belum ada data OPT'; + const praktikBaik = Array.isArray(item.praktik_baik) && item.praktik_baik.length + ? item.praktik_baik.map(value => `
  • ${escapeHtml(value)}
  • `).join('') + : '
  • Belum ada data praktik baik
  • '; return `
    @@ -1449,6 +1413,19 @@ document.addEventListener('DOMContentLoaded', function () { OPT
    ${opt}
    +
    + Praktik Budidaya +
    +
    + Intensitas Pemangkasan + ${escapeHtml(item.pemangkasan || 'Belum ada data')} +
    +
    + Intensitas Pemupukan + ${escapeHtml(item.pemupukan || 'Belum ada data')} +
    + Kebiasaan praktik baik +
      ${praktikBaik}
    `; @@ -1513,7 +1490,7 @@ document.addEventListener('DOMContentLoaded', function () { renderProductionMapLevel(previous.level, previous.parentId); productionMapState.informationLevel = previous.informationLevel; productionMapState.informationId = previous.informationId; - loadProductionMapInformation('production', previous.informationLevel, previous.informationId); + loadProductionMapInformation(previous.informationLevel, previous.informationId); if (previous.bounds?.isValid()) { productionMapState.map.fitBounds(previous.bounds, { padding: [30, 30] }); @@ -1585,7 +1562,8 @@ document.addEventListener('DOMContentLoaded', function () { layer.bindPopup(landPopup(item), { className: 'produksi-map-popup', closeButton: true, - maxWidth: 320, + maxWidth: 340, + maxHeight: 360, }); } else { layer.bindTooltip(regionTooltip(item), { @@ -1606,7 +1584,7 @@ document.addEventListener('DOMContentLoaded', function () { }); productionMapState.informationLevel = nextLevel; productionMapState.informationId = item.id; - loadProductionMapInformation('production', nextLevel, item.id); + loadProductionMapInformation(nextLevel, item.id); renderProductionMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds()); }); } @@ -1641,234 +1619,6 @@ document.addEventListener('DOMContentLoaded', function () { }, 150); } - function initialCultivationMapLevel() { - const levels = mapData.cultivationLevels || {}; - const filters = mapData.filters || {}; - - if (filters.desa && (levels.desa || []).length) return 'desa'; - if (filters.kecamatan && (levels.kecamatan || []).length) return 'kecamatan'; - if (filters.kabupaten && (levels.kabupaten || []).length) return 'kabupaten'; - if ((levels.kabupaten || []).length) return 'kabupaten'; - if ((levels.kecamatan || []).length) return 'kecamatan'; - if ((levels.desa || []).length) return 'desa'; - if ((levels.provinsi || []).length) return 'provinsi'; - - return 'lahan'; - } - - function cultivationPopup(item) { - const praktikBaik = Array.isArray(item.praktik_baik) && item.praktik_baik.length - ? item.praktik_baik.map(value => `
  • ${escapeHtml(value)}
  • `).join('') - : '
  • Belum ada data praktik baik
  • '; - - return ` -
    -
    - Pemilik Lahan -
    ${escapeHtml(item.nama_petani || 'N/A')}
    -
    -
    -
    -
    - Luas Lahan - ${formatNumber(item.luas || 0, 2)} Ha -
    -
    - ID Lahan - TR-${escapeHtml(item.id)} -
    -
    -
    - Intensitas Pemangkasan - ${escapeHtml(item.pemangkasan || 'Belum ada data')} -
    -
    - Intensitas Pemupukan - ${escapeHtml(item.pemupukan || 'Belum ada data')} -
    - Kebiasaan praktik baik -
      ${praktikBaik}
    -
    -
    - `; - } - - const cultivationMapState = { - activeLayer: 'satellite', - backButton: null, - empty: null, - group: null, - history: [], - informationId: null, - informationLevel: null, - layers: {}, - map: null, - }; - - function initCultivationMap() { - const element = document.getElementById('produksi-budidaya-map'); - - if (!element || typeof L === 'undefined') { - return false; - } - - if (cultivationMapState.map) { - return true; - } - - cultivationMapState.layers = { - satellite: L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 22 }), - topo: L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'), - parcels: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'), - }; - cultivationMapState.map = L.map('produksi-budidaya-map', { - center: [-8.67, 121.0], - zoom: 10, - layers: [cultivationMapState.layers.satellite], - zoomControl: false, - }); - cultivationMapState.group = L.featureGroup().addTo(cultivationMapState.map); - cultivationMapState.backButton = document.getElementById('produksi-budidaya-map-back'); - cultivationMapState.empty = document.getElementById('produksi-budidaya-map-empty'); - enableMapInformationInteraction(element); - - L.control.zoom({ position: 'topleft' }).addTo(cultivationMapState.map); - - cultivationMapState.backButton?.addEventListener('click', function () { - const previous = cultivationMapState.history.pop(); - - if (previous) { - renderCultivationMapLevel(previous.level, previous.parentId); - cultivationMapState.informationLevel = previous.informationLevel; - cultivationMapState.informationId = previous.informationId; - loadProductionMapInformation('cultivation', previous.informationLevel, previous.informationId); - - if (previous.bounds?.isValid()) { - cultivationMapState.map.fitBounds(previous.bounds, { padding: [30, 30] }); - } - } - }); - - return true; - } - - function setCultivationMapLayer(type, button) { - if (!cultivationMapState.map || !cultivationMapState.layers[type]) { - return; - } - - Object.values(cultivationMapState.layers).forEach(function (layer) { - cultivationMapState.map.removeLayer(layer); - }); - cultivationMapState.layers[type].addTo(cultivationMapState.map); - cultivationMapState.activeLayer = type; - - document.querySelectorAll('.produksi-cultivation-map-layer-btn').forEach(function (item) { - item.classList.toggle('active', item === button); - }); - } - - function renderCultivationMapLevel(level = null, parentId = null, fallbackBounds = null) { - if (!initCultivationMap()) { - return; - } - - const levels = mapData.cultivationLevels || {}; - const nextLevel = level || initialCultivationMapLevel(); - let dataToRender = levels[nextLevel] || []; - - if (parentId !== null) { - dataToRender = dataToRender.filter(item => String(item.parent_id) === String(parentId)); - } - - cultivationMapState.group.clearLayers(); - - if (nextLevel === 'lahan' && parentId !== null) { - const parentDesa = (levels.desa || []).find(item => String(item.id) === String(parentId)); - const desaLayer = parentDesa - ? makeLayerFromPolygon(parentDesa.polygon, { - color: '#F47C86', - dashArray: '5, 8', - fillOpacity: 0, - interactive: false, - weight: 2, - }) - : null; - - if (desaLayer) { - cultivationMapState.group.addLayer(desaLayer); - } - } - - dataToRender.forEach(function (item) { - const layer = makeLayerFromPolygon(item.polygon, nextLevel === 'lahan' - ? { color: '#5FC7D6', fillColor: '#5FC7D6', fillOpacity: .58, weight: 2 } - : { color: '#9D8EF0', fillColor: '#9D8EF0', fillOpacity: .3, weight: 2 }); - - if (!layer) { - return; - } - - if (nextLevel === 'lahan') { - layer.bindPopup(cultivationPopup(item), { - className: 'produksi-map-popup', - closeButton: true, - maxWidth: 340, - }); - } else { - layer.bindTooltip(regionTooltip(item), { - className: 'produksi-map-tooltip', - direction: 'top', - offset: [0, -10], - sticky: true, - }); - - layer.on('click', function (event) { - L.DomEvent.stopPropagation(event); - cultivationMapState.history.push({ - level: nextLevel, - parentId, - bounds: cultivationMapState.group.getBounds(), - informationLevel: cultivationMapState.informationLevel, - informationId: cultivationMapState.informationId, - }); - cultivationMapState.informationLevel = nextLevel; - cultivationMapState.informationId = item.id; - loadProductionMapInformation('cultivation', nextLevel, item.id); - renderCultivationMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds()); - }); - } - - cultivationMapState.group.addLayer(layer); - }); - - if (cultivationMapState.backButton) { - cultivationMapState.backButton.style.display = cultivationMapState.history.length > 0 ? 'block' : 'none'; - } - - const layersCount = cultivationMapState.group.getLayers().length; - - if (cultivationMapState.empty) { - cultivationMapState.empty.style.display = layersCount > 0 ? 'none' : 'flex'; - } - - if (layersCount > 0) { - const bounds = cultivationMapState.group.getBounds(); - - if (bounds.isValid()) { - cultivationMapState.map.fitBounds(bounds, { padding: [30, 30], maxZoom: 18 }); - } - } else if (fallbackBounds?.isValid()) { - cultivationMapState.map.fitBounds(fallbackBounds, { padding: [30, 30], maxZoom: 18 }); - } else { - cultivationMapState.map.setView([-8.67, 121.0], 10); - } - - setTimeout(function () { - cultivationMapState.map.invalidateSize(); - }, 150); - } - function renderDonut(selector, data, options = {}) { const element = document.querySelector(selector); if (!element) return; @@ -1886,6 +1636,7 @@ document.addEventListener('DOMContentLoaded', function () { stroke: { width: 0 }, dataLabels: { enabled: true, + dropShadow: { enabled: false }, formatter: function (value) { return value.toFixed(0) + '%'; }, @@ -1893,7 +1644,7 @@ document.addEventListener('DOMContentLoaded', function () { fontSize: '13px', fontFamily: fontFamily, fontWeight: 600, - colors: ['#fff'], + colors: chartColors.map(config.chartTextColor), }, }, legend: { @@ -1915,7 +1666,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[seriesIndex] || 'Data'; const color = w.globals.colors?.[seriesIndex] || chartColors[seriesIndex % chartColors.length] || '#9D8EF0'; - return produksiChartTooltip(color, label, value, options.unit || 'data', total); + return produksiChartTooltip(color, label, value, options.unit || 'data', total, 0, config.chartTextColor(color)); }, }, plotOptions: { @@ -1992,6 +1743,7 @@ document.addEventListener('DOMContentLoaded', function () { legend: { show: false }, dataLabels: { enabled: true, + dropShadow: { enabled: false }, formatter: function (value) { return value >= 8 ? value.toFixed(0) + '%' : ''; }, @@ -1999,7 +1751,7 @@ document.addEventListener('DOMContentLoaded', function () { fontSize: '11px', fontFamily: fontFamily, fontWeight: 600, - colors: ['#fff'], + colors: colors.map(config.chartTextColor), }, }, tooltip: { @@ -2008,7 +1760,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[seriesIndex] || 'Data'; const color = w.globals.colors?.[seriesIndex] || colors[seriesIndex % colors.length] || '#9D8EF0'; - return produksiChartTooltip(color, label, value, options.unit || 'data', total); + return produksiChartTooltip(color, label, value, options.unit || 'data', total, 0, config.chartTextColor(color)); }, }, plotOptions: { pie: { expandOnClick: true } }, @@ -2231,7 +1983,7 @@ document.addEventListener('DOMContentLoaded', function () { renderGroupedBar('#chart-sumber-air-kebun', chartData.sumberAirKebun, { unit: 'petani', yTitle: 'Jumlah Petani', height: 330 }); renderGroupedBar('#chart-kebiasaan-praktik-baik', chartData.kebiasaanPraktikBaik, { unit: 'petani', yTitle: 'Jumlah Petani', height: 330 }); renderPieWithLegend('#chart-praktik-sortasi-panen', chartData.praktikSortasiPanen, { unit: 'petani' }); - renderBar('#chart-praktik-fermentasi-pascapanen', chartData.praktikFermentasiPascapanen, { height: 330, color: pastelSemantic.primary || '#9D8EF0', unit: 'petani' }); + renderGroupedBar('#chart-praktik-fermentasi-pascapanen', chartData.praktikFermentasiPascapanen, { unit: 'petani', yTitle: 'Jumlah Petani', height: 330 }); renderActualProductivityIfVisible(); } @@ -2261,37 +2013,15 @@ document.addEventListener('DOMContentLoaded', function () { productionMapRendered = true; } - function renderCultivationMapIfOpen() { - if (!document.getElementById('peta-sebaran-panel')?.classList.contains('show')) { - return; - } - - if (!cultivationMapRendered) { - renderCultivationMapLevel(); - cultivationMapRendered = true; - return; - } - - cultivationMapState.map?.invalidateSize(); - cultivationMapRendered = true; - } - - renderProductionMapInformation('produksi-map-information', mapInformation.production); - renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false); + renderProductionMapInformation('produksi-map-information', mapInformation); if (typeof L === 'undefined') { const mapEmpty = document.getElementById('produksi-map-empty'); - const cultivationMapEmpty = document.getElementById('produksi-budidaya-map-empty'); if (mapEmpty) { mapEmpty.textContent = 'Leaflet belum dimuat.'; mapEmpty.style.display = 'flex'; } - - if (cultivationMapEmpty) { - cultivationMapEmpty.textContent = 'Leaflet belum dimuat.'; - cultivationMapEmpty.style.display = 'flex'; - } } document.querySelectorAll('.produksi-map-layer-btn').forEach(function (button) { @@ -2300,12 +2030,6 @@ document.addEventListener('DOMContentLoaded', function () { }); }); - document.querySelectorAll('.produksi-cultivation-map-layer-btn').forEach(function (button) { - button.addEventListener('click', function () { - setCultivationMapLayer(button.dataset.mapLayer, button); - }); - }); - $('#filter-provinsi, #filter-kabkota, #filter-kecamatan, #filter-desa, #filter-komoditas, #filter-segmen').select2({ width: '100%' }); $('#filter-provinsi').on('change', function () { @@ -2388,7 +2112,6 @@ document.addEventListener('DOMContentLoaded', function () { if (panel.id === 'peta-sebaran-panel' && typeof L !== 'undefined') { renderProductionMapIfOpen(); - renderCultivationMapIfOpen(); } window.dispatchEvent(new Event('resize')); diff --git a/resources/views/layout/header.blade.php b/resources/views/layout/header.blade.php index b80a8d4..474a7ed 100644 --- a/resources/views/layout/header.blade.php +++ b/resources/views/layout/header.blade.php @@ -90,6 +90,6 @@ - + @stack('scripts-head') \ No newline at end of file diff --git a/resources/views/produktivitas/petani/add.blade.php b/resources/views/produktivitas/petani/add.blade.php index c460c44..e2ffc1f 100644 --- a/resources/views/produktivitas/petani/add.blade.php +++ b/resources/views/produktivitas/petani/add.blade.php @@ -234,7 +234,7 @@
    - Kembali + Kembali
    diff --git a/resources/views/produktivitas/petani/akses-pasar/index.blade.php b/resources/views/produktivitas/petani/akses-pasar/index.blade.php index 8252bed..adb7f7b 100644 --- a/resources/views/produktivitas/petani/akses-pasar/index.blade.php +++ b/resources/views/produktivitas/petani/akses-pasar/index.blade.php @@ -365,6 +365,7 @@
    + Kembali diff --git a/resources/views/produktivitas/petani/edit.blade.php b/resources/views/produktivitas/petani/edit.blade.php index 33764db..87cf420 100644 --- a/resources/views/produktivitas/petani/edit.blade.php +++ b/resources/views/produktivitas/petani/edit.blade.php @@ -13,6 +13,7 @@ @push('scripts') +@endpush @section('content')
    @@ -43,13 +44,18 @@
    - + Foto profil petani + +
    @@ -176,7 +184,9 @@ @error('kecamatan_id')
    {{ $message }}
    @enderror
    @@ -184,7 +194,9 @@ @error('desa_kelurahan_id')
    {{ $message }}
    @enderror
    @@ -242,7 +254,7 @@
    - Kembali + Kembali
    @@ -253,6 +265,20 @@
    + + @@ -116,7 +116,7 @@

    @if(!is_null($skorLahanTanaman)) - {{ number_format($totalTanaman, 0, ',', '.') }} tanaman {{ $petani?->komoditas->nama }} + {{ number_format($totalTanaman, 0, ',', '.') }} tanaman {{ $petani?->komoditas?->nama ?? 'Belum diisi' }} @else Belum terisi @endif diff --git a/resources/views/produktivitas/petani/index.blade.php b/resources/views/produktivitas/petani/index.blade.php index 5205818..a0604d1 100644 --- a/resources/views/produktivitas/petani/index.blade.php +++ b/resources/views/produktivitas/petani/index.blade.php @@ -2,6 +2,14 @@ @push('style')