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.
@@ -773,8 +773,8 @@
-
Peta Sebaran Lahan & Tanaman
- Lahan dan tanaman berdasarkan filter wilayah, komoditas, dan segmen
+ Peta Sebaran Lahan, Tanaman & Praktik Budidaya
+ Lahan, tanaman, OPT, pemangkasan, pemupukan, dan praktik baik berdasarkan filter wilayah, komoditas, dan segmen
@@ -795,34 +795,6 @@
-
-
-
-
-
-
-
Peta Praktik Budidaya
- Intensitas pemangkasan, pemupukan, dan kebiasaan praktik baik
-
-
-
-
-
-
-
-
-
-
Belum ada poligon lahan untuk filter ini.
-
-
-
-
-
@@ -1044,8 +1016,8 @@
@@ -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 `
-
`;
@@ -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 `
-
- `;
- }
-
- 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 @@
-
![Lihat foto profil petani]()
+
 : asset('assets/img/avatars/3.jpg') }})
+
+
@@ -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 @@
+
+
+
+
+
+
![Foto profil petani]()
+
+
+
+
+
@@ -344,7 +370,11 @@
let croppieInstance = null;
- $('#fotoPreview').on('click', function () {
+ $('#fotoProfilModal').on('show.bs.modal', function () {
+ $('#fotoProfilBesar').attr('src', $('#fotoPreview').attr('src'));
+ });
+
+ $('#btnGantiFoto').on('click', function () {
$('#foto').val('');
$('#foto').click();
});
diff --git a/resources/views/produktivitas/petani/header.blade.php b/resources/views/produktivitas/petani/header.blade.php
index 170133b..e03f1f5 100644
--- a/resources/views/produktivitas/petani/header.blade.php
+++ b/resources/views/produktivitas/petani/header.blade.php
@@ -58,7 +58,7 @@
Domisili
Alamat: {{ isset($petani) ? $petani->alamat : 'Alamat' }}
-
{{ isset($petani) ? $petani->desaKelurahan->nama : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->nama : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->kabupatenKota->nama : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->kabupatenKota->provinsi->nama : 'Provinsi' }}
+
{{ isset($petani) ? $petani->desaKelurahan?->nama ?? 'Belum diisi' : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->nama ?? 'Belum diisi' : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->nama ?? 'Belum diisi' : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama ?? 'Belum diisi' : 'Provinsi' }}
@@ -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')