colors(); // Progres per enumerator $enumerators = User::query() ->where('role', 'fasilitator') ->withCount([ 'petaniDibuat as done', ]) ->orderByDesc('done') ->get() ->map(function ($enumerator) use ($colors) { $color = $colors[array_rand($colors)]; return [ 'name' => $enumerator->name, 'done' => (int) $enumerator->done, 'color' => $color['color'], 'bg' => $color['bg'], 'tx' => $color['tx'], ]; }) ->values(); // Tabel dan nama model $petaniTable = (new Petani())->getTable(); $komoditasTable = (new Komoditas())->getTable(); $lahanTable = (new Lahan())->getTable(); // Distribusi petani berdasarkan komoditas $komoditasCollection = DB::table("{$petaniTable} as p") ->leftJoin("{$komoditasTable} as k", 'k.id', '=', 'p.komoditas_id') ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as nama") ->selectRaw('COUNT(p.id) as petani_count') ->where('k.is_utama', true) ->groupBy('k.id', 'k.nama') ->orderByDesc('petani_count') ->get(); $totalPetani = (int) $komoditasCollection->sum('petani_count'); $circumference = 2 * pi() * 46; $komoditas = $komoditasCollection ->map(function ($item, $index) use ( $colors, $totalPetani, $circumference ) { $color = $colors[$index % count($colors)]; $ratio = $totalPetani > 0 ? (int) $item->petani_count / $totalPetani : 0; return [ 'label' => $item->nama, 'total' => (int) $item->petani_count, 'pct' => round($ratio * 100, 1), 'arc' => $ratio * $circumference, 'color' => $color['color'], ]; }) ->values(); // Progres pengisian data per petani $progressPetani = Petani::query() ->select([ 'id', 'nama', ]) ->withExists([ 'lahans as has_lahan', 'praktikBudidaya as has_praktik_budidaya', 'sosialEkonomi as has_sosial_ekonomi', 'aksesPasar as has_akses_pasar', ]) ->orderBy('nama') ->get() ->map(function ($petani) { $status = [ 'lahan' => [ 'label' => 'Lahan', 'done' => (bool) $petani->has_lahan, ], 'praktik_budidaya' => [ 'label' => 'Praktik Budidaya', 'done' => (bool) $petani->has_praktik_budidaya, ], 'sosial_ekonomi' => [ 'label' => 'Sosial Ekonomi', 'done' => (bool) $petani->has_sosial_ekonomi, ], 'akses_pasar' => [ 'label' => 'Akses Pasar', 'done' => (bool) $petani->has_akses_pasar, ], ]; $totalModul = count($status); $modulSelesai = collect($status) ->where('done', true) ->count(); return [ 'id' => $petani->id, 'name' => $petani->nama, 'status' => $status, 'done' => $modulSelesai, 'total' => $totalModul, 'percent' => $totalModul > 0 ? (int) round(($modulSelesai / $totalModul) * 100) : 0, ]; }) ->values(); // Ringkasan lahan $totalLuasLahan = (float) Lahan::query() ->sum('luas_lahan'); $totalJumlahTanaman = (int) Lahan::query() ->sum('jumlah_tanaman'); // Jumlah tanaman berdasarkan komoditas petani $tanamanPerKomoditasCollection = DB::table("{$lahanTable} as l") ->join("{$petaniTable} as p", 'p.id', '=', 'l.petani_id') ->leftJoin("{$komoditasTable} as k", 'k.id', '=', 'p.komoditas_id') ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label") ->selectRaw('COALESCE(SUM(l.jumlah_tanaman), 0) as total') ->where('k.is_utama', true) ->groupBy('k.id', 'k.nama') ->orderByDesc('total') ->get(); $tanamanPerKomoditas = $tanamanPerKomoditasCollection ->map(function ($item, $index) use ($colors, $totalJumlahTanaman) { $color = $colors[$index % count($colors)]; $total = (int) $item->total; return [ 'label' => $item->label, 'total' => $total, 'pct' => $totalJumlahTanaman > 0 ? round(($total / $totalJumlahTanaman) * 100, 1) : 0, 'color' => $color['color'], 'bg' => $color['bg'], 'tx' => $color['tx'], ]; }) ->values(); // Distribusi peserta pelatihan GAP $gapOptions = collect($this->pelatihanGapOptions()); $knownGapOptions = $gapOptions->reject(function ($label, $value) { return $this->normalizeText((string) $value) === 'lainnya' || $this->normalizeText((string) $label) === 'lainnya'; }); $gapLookup = []; foreach ($knownGapOptions as $value => $label) { $gapLookup[$this->normalizeText((string) $value)] = (string) $label; $gapLookup[$this->normalizeText((string) $label)] = (string) $label; } $pelatihanPairs = SurveyPelatihan::query() ->whereNotNull('petani_id') ->whereNotNull('jenis_pelatihan') ->where('jenis_pelatihan', '!=', '') ->get([ 'petani_id', 'jenis_pelatihan', ]) ->map(function ($row) use ($gapLookup) { $normalized = $this->normalizeText((string) $row->jenis_pelatihan); return [ 'petani_id' => $row->petani_id, 'label' => $gapLookup[$normalized] ?? 'Lainnya', ]; }) ->unique(fn ($item) => $item['petani_id'] . '|' . $item['label']) ->values(); $pelatihanCounts = $pelatihanPairs->countBy('label'); $pelatihanLabels = $knownGapOptions ->values() ->unique() ->push('Lainnya') ->values(); $pelatihanGap = $pelatihanLabels ->map(function ($label, $index) use ($pelatihanCounts, $colors) { $color = $colors[$index % count($colors)]; return [ 'label' => $label, 'total' => (int) $pelatihanCounts->get($label, 0), 'color' => $color['color'], 'bg' => $color['bg'], 'tx' => $color['tx'], ]; }) ->values(); $totalPetaniPelatihan = $pelatihanPairs ->pluck('petani_id') ->unique() ->count(); // Sertifikasi $totalPetaniBersertifikat = SurveySertifikasi::query() ->whereNotNull('petani_id') ->distinct() ->count('petani_id'); $totalPetaniSertifikatAktif = SurveySertifikasi::query() ->whereNotNull('petani_id') ->where('is_aktif', true) ->distinct() ->count('petani_id'); return view('dashboard.survey-petani', [ 'enumerators' => $enumerators, 'komoditas' => $komoditas, 'totalPetani' => $totalPetani, 'circumference' => $circumference, 'progressPetani' => $progressPetani, 'totalLuasLahan' => $totalLuasLahan, 'totalJumlahTanaman' => $totalJumlahTanaman, 'tanamanPerKomoditas' => $tanamanPerKomoditas, 'pelatihanGap' => $pelatihanGap, 'totalPetaniPelatihan' => $totalPetaniPelatihan, 'totalPetaniBersertifikat' => $totalPetaniBersertifikat, 'totalPetaniSertifikatAktif' => $totalPetaniSertifikatAktif, ]); } private function pelatihanGapOptions(): array { $enumClass = self::GAP_ENUM_CLASS; if (enum_exists($enumClass)) { if (method_exists($enumClass, 'options')) { return collect($enumClass::options()) ->mapWithKeys(function ($option, $key) { if (is_array($option)) { $value = (string) ($option['value'] ?? $key); $label = (string) ( $option['label'] ?? $option['name'] ?? $option['value'] ?? $key ); return [$value => $label]; } return [(string) $key => (string) $option]; }) ->all(); } return collect($enumClass::cases()) ->mapWithKeys(function ($case) { $value = $case instanceof BackedEnum ? (string) $case->value : (string) $case->name; $label = method_exists($case, 'label') ? (string) $case->label() : Str::headline($case->name); return [$value => $label]; }) ->all(); } return [ 'pemangkasan_perawatan_tanaman' => 'Pemangkasan/perawatan tanaman', 'pemupukan_nutrisi_tanah' => 'Pemupukan/nutrisi tanah', 'pengendalian_hama_penyakit' => 'Pengendalian hama & penyakit', 'praktik_berkebun_baik_gap' => 'Praktik Berkebun yang Baik (GAP)', 'pengolahan_pascapanen' => 'Pengolahan/pascapanen', 'penyerbukan_manual_vanili' => 'Penyerbukan manual (vanili)', 'lainnya' => 'Lainnya', ]; } private function normalizeText(string $value): string { return Str::lower(Str::squish($value)); } private function colors(): array { return [ [ 'color' => '#28a745', 'bg' => 'rgba(40,167,69,.15)', 'tx' => '#28a745', ], [ 'color' => '#007bff', 'bg' => 'rgba(0,123,255,.15)', 'tx' => '#007bff', ], [ 'color' => '#fd7e14', 'bg' => 'rgba(253,126,20,.15)', 'tx' => '#fd7e14', ], [ 'color' => '#dc3545', 'bg' => 'rgba(220,53,69,.15)', 'tx' => '#dc3545', ], [ 'color' => '#6f42c1', 'bg' => 'rgba(111,66,193,.15)', 'tx' => '#6f42c1', ], [ 'color' => '#20c997', 'bg' => 'rgba(32,201,151,.15)', 'tx' => '#20c997', ], ]; } }