diff --git a/app/Http/Controllers/Web/Dashboard/SummaryController.php b/app/Http/Controllers/Web/Dashboard/SummaryController.php index dd7028f..b3ffa01 100644 --- a/app/Http/Controllers/Web/Dashboard/SummaryController.php +++ b/app/Http/Controllers/Web/Dashboard/SummaryController.php @@ -262,6 +262,116 @@ class SummaryController extends Controller ], 'levels' => $levels, 'cultivationLevels' => $this->productionCultivationMapLevelData($request, $levels), + 'information' => $this->productionMapInformationData($request), + ]; + } + + private function productionMapInformationData(Request $request): array + { + $landMetrics = $this->karakteristikLandMetrics($request); + $filteredPetaniIds = $this->filteredPetaniIdsQuery($request); + $commodityRows = DB::table('petani') + ->whereNull('petani.deleted_at') + ->whereIn('petani.id', clone $filteredPetaniIds) + ->leftJoin('master_komoditas', 'master_komoditas.id', '=', 'petani.komoditas_id') + ->selectRaw("COALESCE(master_komoditas.nama, 'Belum ditentukan') as nama") + ->selectRaw('COUNT(DISTINCT petani.id) as jumlah') + ->groupBy('master_komoditas.nama') + ->orderByDesc('jumlah') + ->get() + ->map(fn ($row) => ['nama' => $row->nama, 'jumlah' => (int) $row->jumlah]) + ->all(); + + $lahan = (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)) + ->get([ + 'map_lahan.id', + 'map_lahan.serangan_hama', + 'map_lahan.penyakit_tanaman', + 'map_lahan.jenis_hama', + 'map_lahan.jenis_penyakit', + ]); + $tanamanByLahan = DB::table('map_tanaman') + ->whereNull('map_tanaman.deleted_at') + ->whereIn('map_tanaman.lahan_id', $lahan->pluck('id')) + ->get([ + 'map_tanaman.lahan_id', + 'map_tanaman.serangan_hama', + 'map_tanaman.penyakit_tanaman', + 'map_tanaman.jenis_hama', + 'map_tanaman.jenis_penyakit', + ]) + ->groupBy('lahan_id'); + $optCounts = []; + + foreach ($lahan as $land) { + foreach ($this->productionOptDetails($land, $tanamanByLahan->get($land->id, collect())) as $opt) { + $optCounts[$opt] = ($optCounts[$opt] ?? 0) + 1; + } + } + + arsort($optCounts); + + $mainCommodityIds = DB::table('master_komoditas') + ->where('is_utama', true) + ->whereNull('deleted_at') + ->select('id'); + $scoreQuery = DB::table('petani') + ->leftJoin('petani_skor', function ($join) { + $join->on('petani_skor.petani_id', '=', 'petani.id') + ->whereNull('petani_skor.deleted_at'); + }) + ->whereNull('petani.deleted_at') + ->whereIn('petani.id', clone $filteredPetaniIds) + ->whereIn('petani.komoditas_id', $mainCommodityIds); + $averageIndex = (clone $scoreQuery)->avg('petani_skor.skor_kesiapan_adopsi'); + $segmentCase = "CASE + WHEN petani_skor.skor_kesiapan_adopsi >= 4 THEN 'Penggerak' + WHEN petani_skor.skor_kesiapan_adopsi >= 3 THEN 'Perintis' + WHEN petani_skor.skor_kesiapan_adopsi >= 2 THEN 'Pemerhati' + WHEN petani_skor.skor_kesiapan_adopsi >= 1 THEN 'Pemula' + ELSE 'Belum Lengkap' + END"; + $segmentCounts = (clone $scoreQuery) + ->selectRaw("{$segmentCase} as segmen") + ->selectRaw('COUNT(DISTINCT petani.id) as jumlah') + ->groupByRaw($segmentCase) + ->get(); + $segmentPriority = [ + 'Penggerak' => 0, + 'Perintis' => 1, + 'Pemerhati' => 2, + 'Pemula' => 3, + 'Belum Lengkap' => 4, + ]; + $dominantSegment = $segmentCounts + ->sort(function ($left, $right) use ($segmentPriority) { + return ((int) $right->jumlah <=> (int) $left->jumlah) + ?: (($segmentPriority[$left->segmen] ?? 99) <=> ($segmentPriority[$right->segmen] ?? 99)); + }) + ->first(); + $averageSegment = match (true) { + $averageIndex === null => 'Belum Lengkap', + $averageIndex >= 4 => 'Penggerak', + $averageIndex >= 3 => 'Perintis', + $averageIndex >= 2 => 'Pemerhati', + $averageIndex >= 1 => 'Pemula', + default => 'Belum Lengkap', + }; + + return [ + 'jumlah_petani' => $landMetrics['total_petani'], + 'total_luas_lahan' => $landMetrics['total_luas_lahan'], + 'petani_per_komoditas' => $commodityRows, + 'opt' => collect($optCounts) + ->map(fn ($jumlah, $nama) => ['nama' => $nama, 'jumlah' => $jumlah]) + ->values() + ->all(), + 'indeks' => $averageIndex === null ? null : round((float) $averageIndex, 2), + 'segmen' => $averageSegment, + 'segmen_dominan' => $dominantSegment->segmen ?? 'Belum Lengkap', ]; } diff --git a/resources/views/dashboard/produktivitas.blade.php b/resources/views/dashboard/produktivitas.blade.php index ae90cb0..d6b3b29 100644 --- a/resources/views/dashboard/produktivitas.blade.php +++ b/resources/views/dashboard/produktivitas.blade.php @@ -241,6 +241,145 @@ width: 100%; } + .produksi-map-information-overlay { + background: rgba(255, 255, 255, .94); + border: 1px solid rgba(0, 0, 0, .08); + border-radius: 12px; + box-shadow: 0 6px 24px rgba(0, 0, 0, .2); + left: auto; + max-height: calc(100% - 2rem); + overflow-y: auto; + padding: .875rem; + pointer-events: none; + position: absolute; + right: 1rem; + top: 1rem; + width: min(350px, calc(100% - 5.5rem)); + z-index: 700; + } + + .produksi-map-information { + display: flex; + flex-direction: column; + gap: .75rem; + } + + .produksi-map-information-summary { + align-items: center; + background: rgba(var(--bs-primary-rgb, 115, 103, 240), .07); + border-radius: 10px; + display: flex; + gap: .75rem; + padding: .875rem; + } + + .produksi-map-information-summary-icon { + align-items: center; + background: var(--bs-primary, #7367f0); + border-radius: 10px; + color: #fff; + display: inline-flex; + flex: 0 0 38px; + height: 38px; + justify-content: center; + width: 38px; + } + + .produksi-map-information-summary-value { + color: var(--bs-heading-color); + font-size: 1.25rem; + font-weight: 700; + line-height: 1.2; + } + + .produksi-map-information-summary-label, + .produksi-map-information-section-title { + color: var(--bs-heading-color); + font-size: .8125rem; + font-weight: 600; + } + + .produksi-map-information-metrics { + display: grid; + gap: .4rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .produksi-map-information-metric { + background: rgba(var(--bs-primary-rgb, 115, 103, 240), .07); + border-radius: 8px; + min-width: 0; + padding: .5rem .625rem; + } + + .produksi-map-information-metric-label { + color: var(--bs-secondary-color); + display: block; + font-size: .65rem; + line-height: 1.2; + margin-bottom: .2rem; + } + + .produksi-map-information-metric-value { + color: var(--bs-heading-color); + display: block; + font-size: .75rem; + font-weight: 700; + line-height: 1.25; + overflow-wrap: anywhere; + } + + .produksi-map-information-section-title { + display: block; + margin-bottom: .625rem; + } + + .produksi-map-information-bars { + display: flex; + flex-direction: column; + gap: .5rem; + } + + .produksi-map-information-bar-label { + align-items: baseline; + color: var(--bs-body-color); + display: flex; + font-size: .75rem; + gap: .5rem; + justify-content: space-between; + margin-bottom: .25rem; + } + + .produksi-map-information-bar-label span:first-child { + min-width: 0; + overflow-wrap: anywhere; + } + + .produksi-map-information-bar-label strong { + color: var(--bs-heading-color); + flex: 0 0 auto; + font-weight: 600; + } + + .produksi-map-information-track { + background: var(--bs-secondary-bg, rgba(128, 128, 128, .14)); + border-radius: 999px; + height: 6px; + overflow: hidden; + } + + .produksi-map-information-fill { + background: var(--bs-primary, #7367f0); + border-radius: inherit; + height: 100%; + min-width: 3px; + } + + .produksi-map-information-empty { + color: var(--bs-secondary-color); + font-size: .75rem; + } + .produksi-map-back { display: none; left: 50px; @@ -479,6 +618,13 @@ left: 16px; top: 16px; } + + .produksi-map-information-overlay { + left: auto; + right: .75rem; + top: 1rem; + width: min(320px, calc(100% - 1.5rem)); + } } @endpush @@ -618,51 +764,61 @@