From 80f0056efeddda909f138344b6528fbbadb409b1 Mon Sep 17 00:00:00 2001 From: SyaharaniSalsabilla Date: Tue, 13 Oct 2026 15:55:18 +0700 Subject: [PATCH] dashboard produksi & ganti warna chart --- .../FrekuensiPemangkasanPendamping.php | 4 +- .../FrekuensiPemangkasanUtama.php | 2 +- .../MetodePenangananHamaPenyakit.php | 4 +- .../Web/Dashboard/SummaryController.php | 220 +++++++- .../Web/Dashboard/SurveyPetaniController.php | 36 +- public/assets/js/config.js | 40 ++ public/assets/js/dashboards-analytics.js | 53 +- .../views/dashboard/akselerasi.blade.php | 22 +- resources/views/dashboard/dashboard.blade.php | 54 +- .../views/dashboard/hilirisasi.blade.php | 24 +- .../dashboard/indikator-pertanian.blade.php | 48 +- .../dashboard/karakteristik-petani.blade.php | 189 ++++--- .../views/dashboard/konektivitas.blade.php | 25 +- resources/views/dashboard/petani.blade.php | 5 +- .../views/dashboard/produktivitas.blade.php | 473 +++++++++++++++--- .../views/dashboard/resiliensi.blade.php | 53 +- resources/views/dashboard/summary.blade.php | 43 +- .../views/dashboard/survey-petani.blade.php | 18 +- .../profil-petani/index.blade.php | 15 +- 19 files changed, 994 insertions(+), 334 deletions(-) diff --git a/app/Enums/PraktikBudidaya/FrekuensiPemangkasanPendamping.php b/app/Enums/PraktikBudidaya/FrekuensiPemangkasanPendamping.php index f6b87b7..ec52eb1 100644 --- a/app/Enums/PraktikBudidaya/FrekuensiPemangkasanPendamping.php +++ b/app/Enums/PraktikBudidaya/FrekuensiPemangkasanPendamping.php @@ -8,7 +8,7 @@ enum FrekuensiPemangkasanPendamping: string case TIDAK_RUTIN = 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya'; case SATU_KALI_TAHUN = 'Memangkas 1 kali/tahun, tahu jenis tekniknya'; case DUA_KALI_TAHUN = 'Memangkas 2 kali/tahun, tahu jenis tekniknya'; - case LEBIH_DUA_KALI_TAHUN = 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin'; + case LEBIH_DUA_KALI_TAHUN = 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin'; public function label(): string { @@ -17,7 +17,7 @@ enum FrekuensiPemangkasanPendamping: string self::TIDAK_RUTIN => 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya', self::SATU_KALI_TAHUN => 'Memangkas 1 kali/tahun, tahu jenis tekniknya', self::DUA_KALI_TAHUN => 'Memangkas 2 kali/tahun, tahu jenis tekniknya', - self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin', + self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin', }; } diff --git a/app/Enums/PraktikBudidaya/FrekuensiPemangkasanUtama.php b/app/Enums/PraktikBudidaya/FrekuensiPemangkasanUtama.php index 0444c65..e6430b1 100644 --- a/app/Enums/PraktikBudidaya/FrekuensiPemangkasanUtama.php +++ b/app/Enums/PraktikBudidaya/FrekuensiPemangkasanUtama.php @@ -17,7 +17,7 @@ enum FrekuensiPemangkasanUtama: int self::TIDAK_RUTIN => 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya', self::SATU_KALI_TAHUN => 'Memangkas 1 kali/tahun, tahu jenis tekniknya', self::DUA_KALI_TAHUN => 'Memangkas 2 kali/tahun, tahu jenis tekniknya', - self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin', + self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin', }; } diff --git a/app/Enums/PraktikBudidaya/MetodePenangananHamaPenyakit.php b/app/Enums/PraktikBudidaya/MetodePenangananHamaPenyakit.php index 9936527..ef8ffe4 100644 --- a/app/Enums/PraktikBudidaya/MetodePenangananHamaPenyakit.php +++ b/app/Enums/PraktikBudidaya/MetodePenangananHamaPenyakit.php @@ -16,8 +16,8 @@ enum MetodePenangananHamaPenyakit: int self::TIDAK_ADA => 'Tidak ada penanganan', self::CARA_TRADISIONAL => 'Cara tradisional (doa/ritual)', self::PESTISIDA_SANITASI => 'Pestisida atau sanitasi', - self::KOMBINASI => 'Kombinasi pestisada dan sanitasi', - self::PHT => 'Pengendalian hama terpadu(PHT)', + self::KOMBINASI => 'Kombinasi pestisida dan sanitasi', + self::PHT => 'Pengendalian hama terpadu (PHT)', }; } diff --git a/app/Http/Controllers/Web/Dashboard/SummaryController.php b/app/Http/Controllers/Web/Dashboard/SummaryController.php index c545f94..d374c51 100644 --- a/app/Http/Controllers/Web/Dashboard/SummaryController.php +++ b/app/Http/Controllers/Web/Dashboard/SummaryController.php @@ -5,8 +5,13 @@ namespace App\Http\Controllers\Web\Dashboard; use App\Enums\KeparahanSerangan; use App\Enums\KondisiTanaman; use App\Enums\PenyakitTanaman; +use App\Enums\PraktikBudidaya\FrekuensiPemangkasanUtama; +use App\Enums\PraktikBudidaya\FrekuensiSanitasiLahan; +use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit; +use App\Enums\PraktikBudidaya\PemberianPupuk; use App\Enums\SeranganHama; use App\Http\Controllers\Controller; +use App\Models\Pertanian\Komoditas; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; @@ -67,6 +72,7 @@ class SummaryController extends Controller ->get([ 'id', 'lahan_id', + 'komoditas_id', 'kondisi_tanaman', 'umur_tanaman', 'serangan_hama', @@ -87,49 +93,189 @@ class SummaryController extends Controller $landMetrics = $this->karakteristikLandMetrics($request); $totalLuasLahan = $landMetrics['total_luas_lahan']; - $petaniCount = $landMetrics['total_petani']; $avgLuasPerPetani = $landMetrics['ratarata_luas_lahan']; - $jumlahTanaman = $conditionSource->whereNotNull('kondisi_tanaman')->count(); + $productivityCards = $this->productivityKpiCards($lahanIds); + $praktikBudidaya = $this->filteredPraktikBudidaya($request); $kpiCards = [ [ 'value' => number_format($totalLuasLahan, 2, ',', '.'), - 'unit' => 'Ha', + 'unit' => 'ha', 'label' => 'Total Luas Lahan', - 'description' => 'Luas keseluruhan lahan terfilter', + 'description' => 'Seluruh lahan petani', 'icon' => 'tabler-chart-area-line', 'variant' => 'success', ], [ 'value' => number_format($avgLuasPerPetani, 2, ',', '.'), - 'unit' => 'Ha/petani', - 'label' => 'Rata-Rata Luas Lahan per Petani', - 'description' => number_format($petaniCount, 0, ',', '.').' petani terfilter', + 'unit' => 'ha', + 'label' => 'Rata-rata Luas Lahan', + 'description' => 'Rata-rata per petani', 'icon' => 'tabler-ruler-measure', 'variant' => 'danger', ], - [ - 'value' => number_format($jumlahTanaman, 0, ',', '.'), - 'unit' => 'tanaman', - 'label' => 'Jumlah Tanaman', - 'description' => 'Total tanaman pada chart kondisi tanaman', - 'icon' => 'tabler-plant-2', - 'variant' => 'secondary', - ], ]; + $kpiCards = array_merge($kpiCards, $productivityCards); return [ 'kpiCards' => $kpiCards, 'chartData' => [ - 'umurLahan' => $this->ageBucketChart($lahan, 'umur_lahan'), 'umurTanaman' => $this->ageBucketChart($healthSource, 'umur_tanaman'), + 'jumlahTanamanPerKomoditas' => $this->plantCountByCommodityChart($tanaman, $lahan), 'kondisiTanaman' => $this->attributeChart($conditionSource, 'kondisi_tanaman', fn ($value) => $this->conditionLabel($value)), 'keparahanSerangan' => $this->attributeChart($healthSource, 'keparahan_serangan', fn ($value) => $this->severityLabel($value)), 'gejalaSerangan' => $this->symptomChart($healthSource), + 'intensitasPemangkasan' => $this->enumOptionChart($praktikBudidaya, 'frekuensi_pemangkasan_utama', FrekuensiPemangkasanUtama::class), + 'intensitasPemupukan' => $this->enumOptionChart($praktikBudidaya, 'pemberian_pupuk', PemberianPupuk::class), + 'metodePenangananHamaPenyakit' => $this->enumOptionChart($praktikBudidaya, 'metode_penanganan_opt', MetodePenangananHamaPenyakit::class), + 'intensitasPembersihanLahan' => $this->enumOptionChart($praktikBudidaya, 'frekuensi_sanitasi_lahan', FrekuensiSanitasiLahan::class), + 'sumberAirKebun' => $this->arrayOptionChart( + $praktikBudidaya, + 'sumber_air', + ['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja', 'Lainnya'], + fn ($value) => in_array($value, ['Sungai/mata air', 'Sumur', 'Mengandalkan hujan saja'], true) ? $value : 'Lainnya', + ), + 'kebiasaanPraktikBaik' => $this->arrayOptionChart( + $praktikBudidaya, + 'kebiasaan_konservasi_tanah', + [ + 'Membuat pematang/tanggul tanah di lahan miring supaya tanah tidak longsor ke bawah', + 'Menumpuk daun kering/sisa tanaman di sekitar pohon', + 'Menanam tanaman lain di sela-sela komoditas utama untuk menutupi tanah', + 'Membuat lubang atau parit kecil untuk menampung air hujan', + 'Tidak ada yang seperti itu di kebun saya', + ], + fn ($value) => $value === 'Membuat pematang atau 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, + ), ], ]; } + private function filteredPraktikBudidaya(Request $request) + { + $petaniIds = $this->filteredPetaniIdsQuery($request); + + return DB::table('praktik_budidaya') + ->whereNull('praktik_budidaya.deleted_at') + ->whereIn('praktik_budidaya.petani_id', $petaniIds) + ->get([ + 'frekuensi_pemangkasan_utama', + 'pemberian_pupuk', + 'metode_penanganan_opt', + 'frekuensi_sanitasi_lahan', + 'sumber_air', + 'kebiasaan_konservasi_tanah', + ]); + } + + private function plantCountByCommodityChart($tanaman, $lahan): array + { + $lahanKomoditas = $lahan + ->mapWithKeys(fn ($row) => [(int) $row->id => $row->komoditas_nama ?: 'Belum ditentukan']); + + $komoditasNames = DB::table('master_komoditas') + ->whereNull('deleted_at') + ->pluck('nama', 'id'); + + $counts = $tanaman + ->map(function ($row) use ($komoditasNames, $lahanKomoditas) { + if ($row->komoditas_id && $komoditasNames->has($row->komoditas_id)) { + return $komoditasNames[$row->komoditas_id]; + } + + return $lahanKomoditas[(int) $row->lahan_id] ?? 'Belum ditentukan'; + }) + ->filter() + ->countBy() + ->sortDesc(); + + return [ + 'labels' => $counts->keys()->values()->all(), + 'series' => $counts->values()->map(fn ($value) => (int) $value)->values()->all(), + ]; + } + + private function productivityKpiCards($lahanIds): array + { + $komoditas = Komoditas::query() + ->where('is_utama', true) + ->orderBy('nama') + ->limit(4) + ->get(['id', 'nama']); + + $produksiRows = DB::table('produksi') + ->whereNull('produksi.deleted_at') + ->leftJoin('map_lahan as lahan_produksi', 'lahan_produksi.id', '=', 'produksi.lahan_id') + ->when($lahanIds->isNotEmpty(), fn ($query) => $query->whereIn('produksi.lahan_id', $lahanIds)) + ->when($lahanIds->isEmpty(), fn ($query) => $query->whereRaw('1 = 0')) + ->selectRaw('COALESCE(produksi.komoditas_id, lahan_produksi.komoditas_id) as komoditas_id') + ->addSelect([ + 'produksi.jumlah_panen', + 'produksi.satuan', + 'produksi.luas_panen', + 'produksi.produktivitas', + ]) + ->get() + ->groupBy('komoditas_id'); + + $variants = ['primary', 'warning', 'info', 'secondary']; + $icon = 'tabler-chart-bar'; + + return $komoditas + ->values() + ->map(function ($item, int $index) use ($produksiRows, $variants, $icon) { + $rows = $produksiRows->get($item->id, collect()); + $average = $this->averageProductivity($rows); + + return [ + 'value' => number_format($average, 0, ',', '.'), + 'unit' => 'kg/ha', + 'label' => 'Produktivitas Rata-rata '.$item->nama, + 'description' => 'Rata-rata produktivitas per komoditas', + 'icon' => $icon, + 'variant' => $variants[$index % count($variants)], + ]; + }) + ->all(); + } + + private function averageProductivity($rows): float + { + if ($rows->isEmpty()) { + return 0; + } + + $storedAverage = $rows + ->pluck('produktivitas') + ->filter(fn ($value) => (float) $value > 0) + ->avg(); + + if ($storedAverage > 0) { + return round((float) $storedAverage, 2); + } + + $totalProduksiKg = $rows->sum(fn ($row) => $this->toKilograms( + (float) ($row->jumlah_panen ?? 0), + (string) ($row->satuan ?? 'kg') + )); + $totalLuasPanen = $rows->sum(fn ($row) => (float) ($row->luas_panen ?? 0)); + + return $totalLuasPanen > 0 + ? round($totalProduksiKg / $totalLuasPanen, 2) + : 0; + } + + private function toKilograms(float $value, string $unit): float + { + return match (strtolower($unit)) { + 'ton' => $value * 1000, + 'kuintal', 'kwintal' => $value * 100, + default => $value, + }; + } + private function filteredLahanQuery(Request $request) { return DB::table('map_lahan') @@ -271,6 +417,48 @@ class SummaryController extends Controller ]; } + private function enumOptionChart($rows, string $field, string $enumClass): array + { + $options = collect($enumClass::options()) + ->mapWithKeys(fn ($option) => [(string) $option['value'] => $option['label']]); + + $counts = $rows + ->pluck($field) + ->filter(fn ($value) => $value !== null && $value !== '') + ->map(fn ($value) => (string) $value) + ->countBy(); + + return [ + 'labels' => $options->values()->all(), + 'series' => $options->keys()->map(fn ($value) => (int) ($counts[$value] ?? 0))->values()->all(), + ]; + } + + private function arrayOptionChart($rows, string $field, array $labels, ?callable $normalizer = null): array + { + $counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]); + + foreach ($rows as $row) { + foreach ($this->arrayValues($row->{$field} ?? null) as $value) { + $label = $normalizer ? $normalizer($value) : $value; + if ($label === null || $label === '') { + continue; + } + + if (!$counts->has($label)) { + $counts[$label] = 0; + } + + $counts[$label] = (int) $counts[$label] + 1; + } + } + + return [ + 'labels' => $counts->keys()->values()->all(), + 'series' => $counts->values()->map(fn ($value) => (int) $value)->values()->all(), + ]; + } + private function symptomChart($rows): array { $counts = collect(); diff --git a/app/Http/Controllers/Web/Dashboard/SurveyPetaniController.php b/app/Http/Controllers/Web/Dashboard/SurveyPetaniController.php index f35b373..6ce9279 100644 --- a/app/Http/Controllers/Web/Dashboard/SurveyPetaniController.php +++ b/app/Http/Controllers/Web/Dashboard/SurveyPetaniController.php @@ -489,34 +489,34 @@ class SurveyPetaniController extends Controller { return [ [ - 'color' => '#28a745', - 'bg' => 'rgba(40,167,69,.15)', - 'tx' => '#28a745', + 'color' => '#6FD68F', + 'bg' => 'rgba(111,214,143,.24)', + 'tx' => '#6FD68F', ], [ - 'color' => '#007bff', - 'bg' => 'rgba(0,123,255,.15)', - 'tx' => '#007bff', + 'color' => '#5FC7D6', + 'bg' => 'rgba(95,199,214,.24)', + 'tx' => '#5FC7D6', ], [ - 'color' => '#fd7e14', - 'bg' => 'rgba(253,126,20,.15)', - 'tx' => '#fd7e14', + 'color' => '#FFB46F', + 'bg' => 'rgba(255,180,111,.26)', + 'tx' => '#FFB46F', ], [ - 'color' => '#dc3545', - 'bg' => 'rgba(220,53,69,.15)', - 'tx' => '#dc3545', + 'color' => '#F47C86', + 'bg' => 'rgba(244,124,134,.26)', + 'tx' => '#F47C86', ], [ - 'color' => '#6f42c1', - 'bg' => 'rgba(111,66,193,.15)', - 'tx' => '#6f42c1', + 'color' => '#9D8EF0', + 'bg' => 'rgba(157,142,240,.26)', + 'tx' => '#9D8EF0', ], [ - 'color' => '#20c997', - 'bg' => 'rgba(32,201,151,.15)', - 'tx' => '#20c997', + 'color' => '#65D8BD', + 'bg' => 'rgba(101,216,189,.26)', + 'tx' => '#65D8BD', ], ]; } diff --git a/public/assets/js/config.js b/public/assets/js/config.js index 47b3dd9..128fc68 100644 --- a/public/assets/js/config.js +++ b/public/assets/js/config.js @@ -37,6 +37,46 @@ window.config = { danger: window.Helpers.getCssVar('danger-bg-subtle'), dark: window.Helpers.getCssVar('dark-bg-subtle') }, + chartColors: { + series: ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0'], + semantic: { + primary: '#9D8EF0', + secondary: '#9EA6B0', + success: '#6FD68F', + info: '#5FC7D6', + warning: '#FFB46F', + danger: '#F47C86', + purple: '#9D8EF0', + teal: '#65D8BD', + pink: '#F47C86', + gray: '#9EA6B0' + }, + text: { + primary: '#7367F0', + secondary: '#808390', + success: '#28C76F', + info: '#00BAD1', + warning: '#FF9F43', + danger: '#FF4C51', + purple: '#7367F0', + teal: '#20C997', + pink: '#FF4C51', + gray: '#808390' + }, + categories: { + coffee: '#6FD68F', + vanilla: '#FFB46F', + cacao: '#9D8EF0', + candlenut: '#9EA6B0', + shade: '#65D8BD', + policyPurple: '#9D8EF0', + policyPink: '#F47C86', + marketGreen: '#6FD68F', + marketRed: '#F47C86', + marketBlue: '#5FC7D6' + }, + grid: '#ECF0F6' + }, fontFamily: window.Helpers.getCssVar('font-family-base'), enableMenuLocalStorage: true // Enable menu state with local storage support }; diff --git a/public/assets/js/dashboards-analytics.js b/public/assets/js/dashboards-analytics.js index fef9fad..40324e8 100644 --- a/public/assets/js/dashboards-analytics.js +++ b/public/assets/js/dashboards-analytics.js @@ -9,6 +9,9 @@ cardColor = config.colors.cardColor; labelColor = config.colors.textMuted; headingColor = config.colors.headingColor; + const pastelChart = config.chartColors || {}; + const pastelSemantic = pastelChart.semantic || {}; + const pastelSeries = pastelChart.series || ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0']; // swiper loop and autoplay // -------------------------------------------------------------------- @@ -48,16 +51,10 @@ grid: { show: false }, - colors: [config.colors.success], + colors: [pastelSemantic.success || '#6FD68F'], fill: { - type: 'gradient', - gradient: { - shadeIntensity: 1, - opacityFrom: 0.4, - gradientToColors: [config.colors.cardColor], - opacityTo: 0.1, - stops: [0, 100] - } + type: 'solid', + opacity: 0.22 }, dataLabels: { enabled: false @@ -151,13 +148,13 @@ } }, colors: [ - config.colors_label.primary, - config.colors_label.primary, - config.colors_label.primary, - config.colors_label.primary, - config.colors.primary, - config.colors_label.primary, - config.colors_label.primary + pastelSemantic.secondary || '#9EA6B0', + pastelSemantic.secondary || '#9EA6B0', + pastelSemantic.secondary || '#9EA6B0', + pastelSemantic.secondary || '#9EA6B0', + pastelSemantic.primary || '#9D8EF0', + pastelSemantic.secondary || '#9EA6B0', + pastelSemantic.secondary || '#9EA6B0' ], dataLabels: { enabled: false @@ -262,18 +259,10 @@ } } }, - colors: [config.colors.primary], + colors: [pastelSemantic.primary || '#9D8EF0'], fill: { - type: 'gradient', - gradient: { - shade: 'dark', - shadeIntensity: 0.5, - gradientToColors: [config.colors.primary], - inverseColors: true, - opacityFrom: 1, - opacityTo: 0.6, - stops: [30, 70, 100] - } + type: 'solid', + opacity: 0.9 }, stroke: { dashArray: 10 @@ -356,7 +345,10 @@ } }, - colors: [config.colors.primary, config.colors.secondary], + colors: [ + pastelSeries[0] || '#9D8EF0', + pastelSeries[1] || '#6FD68F' + ], dataLabels: { enabled: false }, @@ -369,7 +361,10 @@ legend: { show: false }, - colors: [config.colors.primary, config.colors.secondary], + colors: [ + pastelSeries[0] || '#9D8EF0', + pastelSeries[1] || '#6FD68F' + ], fill: { opacity: 1 }, diff --git a/resources/views/dashboard/akselerasi.blade.php b/resources/views/dashboard/akselerasi.blade.php index e89e489..9919495 100644 --- a/resources/views/dashboard/akselerasi.blade.php +++ b/resources/views/dashboard/akselerasi.blade.php @@ -238,6 +238,10 @@ @push('scripts') -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/dashboard/dashboard.blade.php b/resources/views/dashboard/dashboard.blade.php index a2b2f91..639956e 100644 --- a/resources/views/dashboard/dashboard.blade.php +++ b/resources/views/dashboard/dashboard.blade.php @@ -7,9 +7,9 @@ transition: transform .2s ease; } .tk-stat:hover { transform: translateX(4px); } - .tk-stat.green { border-left-color: #28a745; } - .tk-stat.blue { border-left-color: #007bff; } - .tk-stat.amber { border-left-color: #fd7e14; } + .tk-stat.green { border-left-color: #6FD68F; } + .tk-stat.blue { border-left-color: #5FC7D6; } + .tk-stat.amber { border-left-color: #FFB46F; } .tk-stat-lbl { font-size: 10px; text-transform: uppercase; @@ -88,38 +88,38 @@ 'new_month' => 12, 'enumerators' => [ - ['name'=>'Enumerator Ngada 1','done'=>72,'target'=>80,'color'=>'#28a745','bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'], - ['name'=>'Enumerator Ngada 2','done'=>58,'target'=>80,'color'=>'#007bff','bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'], - ['name'=>'Enumerator Ngada 3','done'=>48,'target'=>80,'color'=>'#fd7e14','bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'], - ['name'=>'Enumerator Ngada 4','done'=>36,'target'=>80,'color'=>'#dc3545','bg'=>'rgba(220,53,69,.15)','tx'=>'#dc3545'], + ['name'=>'Enumerator Ngada 1','done'=>72,'target'=>80,'color'=>'#6FD68F','bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'], + ['name'=>'Enumerator Ngada 2','done'=>58,'target'=>80,'color'=>'#5FC7D6','bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'], + ['name'=>'Enumerator Ngada 3','done'=>48,'target'=>80,'color'=>'#FFB46F','bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'], + ['name'=>'Enumerator Ngada 4','done'=>36,'target'=>80,'color'=>'#F47C86','bg'=>'rgba(244,124,134,.26)','tx'=>'#F47C86'], ], 'recent' => [ - ['code'=>'TKD-2026-0178','name'=>'Markus Bela', 'init'=>'MB','desa'=>'Wangka', 'date'=>'24 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'], - ['code'=>'TKD-2026-0177','name'=>'Theresia Nuka','init'=>'TN','desa'=>'Langa', 'date'=>'23 Jun 2026','pct'=>67, 'status'=>'in_progress','bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'], - ['code'=>'TKD-2026-0176','name'=>'Yosef Meo', 'init'=>'YM','desa'=>'Bobo', 'date'=>'22 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'], - ['code'=>'TKD-2026-0175','name'=>'Maria Seda', 'init'=>'MS','desa'=>'Tana Wolo','date'=>'21 Jun 2026','pct'=>22, 'status'=>'draft', 'bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'], - ['code'=>'TKD-2026-0174','name'=>'Rafael Bolo', 'init'=>'RB','desa'=>'Wangka', 'date'=>'20 Jun 2026','pct'=>11, 'status'=>'draft', 'bg'=>'rgba(220,53,69,.15)','tx'=>'#dc3545'], + ['code'=>'TKD-2026-0178','name'=>'Markus Bela', 'init'=>'MB','desa'=>'Wangka', 'date'=>'24 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'], + ['code'=>'TKD-2026-0177','name'=>'Theresia Nuka','init'=>'TN','desa'=>'Langa', 'date'=>'23 Jun 2026','pct'=>67, 'status'=>'in_progress','bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'], + ['code'=>'TKD-2026-0176','name'=>'Yosef Meo', 'init'=>'YM','desa'=>'Bobo', 'date'=>'22 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'], + ['code'=>'TKD-2026-0175','name'=>'Maria Seda', 'init'=>'MS','desa'=>'Tana Wolo','date'=>'21 Jun 2026','pct'=>22, 'status'=>'draft', 'bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'], + ['code'=>'TKD-2026-0174','name'=>'Rafael Bolo', 'init'=>'RB','desa'=>'Wangka', 'date'=>'20 Jun 2026','pct'=>11, 'status'=>'draft', 'bg'=>'rgba(244,124,134,.26)','tx'=>'#F47C86'], ], 'komoditas' => [ - ['label'=>'Kopi', 'total'=>148,'pct'=>60,'color'=>'#28a745','arc'=>174], - ['label'=>'Kakao', 'total'=>82, 'pct'=>33,'color'=>'#007bff','arc'=>96], - ['label'=>'Vanili','total'=>33, 'pct'=>13,'color'=>'#fd7e14','arc'=>38], - ['label'=>'Kemiri','total'=>22, 'pct'=>9, 'color'=>'#6f42c1','arc'=>26], + ['label'=>'Kopi', 'total'=>148,'pct'=>60,'color'=>'#6FD68F','arc'=>174], + ['label'=>'Kakao', 'total'=>82, 'pct'=>33,'color'=>'#9D8EF0','arc'=>96], + ['label'=>'Vanili','total'=>33, 'pct'=>13,'color'=>'#FFB46F','arc'=>38], + ['label'=>'Kemiri','total'=>22, 'pct'=>9, 'color'=>'#9EA6B0','arc'=>26], ], 'kecamatan' => [ - ['name'=>'Bajawa','total'=>89,'pct'=>78,'color'=>'#28a745'], - ['name'=>'Soa', 'total'=>68,'pct'=>59,'color'=>'#007bff'], - ['name'=>'Golewa','total'=>57,'pct'=>50,'color'=>'#fd7e14'], + ['name'=>'Bajawa','total'=>89,'pct'=>78,'color'=>'#6FD68F'], + ['name'=>'Soa', 'total'=>68,'pct'=>59,'color'=>'#5FC7D6'], + ['name'=>'Golewa','total'=>57,'pct'=>50,'color'=>'#FFB46F'], ], 'pending' => [ - ['name'=>'Theresia Nuka','section'=>'D4-K3','pct'=>67,'bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'], - ['name'=>'Maria Seda', 'section'=>'D2-K2','pct'=>22,'bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'], - ['name'=>'Rafael Bolo', 'section'=>'D2-K1','pct'=>11,'bg'=>'rgba(220,53,69,.15)', 'tx'=>'#dc3545'], - ['name'=>'Antonius Wea', 'section'=>'D2-K1','pct'=>0, 'bg'=>'rgba(220,53,69,.15)', 'tx'=>'#dc3545'], + ['name'=>'Theresia Nuka','section'=>'D4-K3','pct'=>67,'bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'], + ['name'=>'Maria Seda', 'section'=>'D2-K2','pct'=>22,'bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'], + ['name'=>'Rafael Bolo', 'section'=>'D2-K1','pct'=>11,'bg'=>'rgba(244,124,134,.26)', 'tx'=>'#F47C86'], + ['name'=>'Antonius Wea', 'section'=>'D2-K1','pct'=>0, 'bg'=>'rgba(244,124,134,.26)', 'tx'=>'#F47C86'], ], 'pending_more' => 32, ]; @@ -258,10 +258,10 @@ @foreach($d['recent'] as $s) @php $bc = match(true) { - $s['pct'] === 100 => '#28a745', - $s['pct'] >= 50 => '#007bff', - $s['pct'] >= 20 => '#fd7e14', - default => '#dc3545', + $s['pct'] === 100 => '#6FD68F', + $s['pct'] >= 50 => '#5FC7D6', + $s['pct'] >= 20 => '#FFB46F', + default => '#F47C86', }; @endphp diff --git a/resources/views/dashboard/hilirisasi.blade.php b/resources/views/dashboard/hilirisasi.blade.php index 73f3b73..b97b3db 100644 --- a/resources/views/dashboard/hilirisasi.blade.php +++ b/resources/views/dashboard/hilirisasi.blade.php @@ -208,6 +208,10 @@ @push('scripts') -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/dashboard/indikator-pertanian.blade.php b/resources/views/dashboard/indikator-pertanian.blade.php index 27cf0d8..b4b9b78 100644 --- a/resources/views/dashboard/indikator-pertanian.blade.php +++ b/resources/views/dashboard/indikator-pertanian.blade.php @@ -60,8 +60,8 @@ } .indicator-filter-bar .form-select:focus { - border-color: rgba(40, 167, 69, .55); - box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12); + border-color: rgba(111, 214, 143, .65); + box-shadow: 0 0 0 .2rem rgba(111, 214, 143, .20); } .indicator-filter-bar.is-loading .form-select, @@ -190,7 +190,7 @@ .indicator-commodity-chip { align-items: center; - background: linear-gradient(180deg, rgba(40, 199, 111, .08), rgba(40, 199, 111, .02)); + background: rgba(111, 214, 143, .20); border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius); display: flex; @@ -201,7 +201,7 @@ .indicator-commodity-icon { align-items: center; - background-color: rgba(40, 199, 111, .16); + background-color: rgba(111, 214, 143, .24); border-radius: var(--bs-border-radius); color: var(--bs-success); display: inline-flex; @@ -296,11 +296,11 @@ } .bg-success-light { - background-color: rgba(40, 167, 69, .1); + background-color: rgba(104, 193, 124, .20); } .bg-primary-light { - background-color: rgba(0, 123, 255, .1); + background-color: rgba(95, 199, 214, .20); } .stat-value { @@ -1658,7 +1658,7 @@ const parentDesa = (levels.desa || []).find(item => String(item.id) === String(parentId)); const desaLayer = parentDesa ? makeLayerFromPolygon(parentDesa.polygon, { - color: '#dc3545', + color: '#F47C86', dashArray: '5, 8', fillOpacity: 0, interactive: false, @@ -1673,8 +1673,8 @@ dataToRender.forEach(function (item) { const layer = makeLayerFromPolygon(item.polygon, nextLevel === 'lahan' - ? { color: '#28a745', fillColor: '#28a745', fillOpacity: .6, weight: 2 } - : { color: '#f39c12', fillColor: '#f1c40f', fillOpacity: .3, weight: 2 }); + ? { color: '#6FD68F', fillColor: '#6FD68F', fillOpacity: .6, weight: 2 } + : { color: '#FFB46F', fillColor: '#FFB46F', fillOpacity: .3, weight: 2 }); if (!layer) { return; @@ -1889,20 +1889,23 @@ } const appConfig = typeof config !== 'undefined' ? config : {}; + const pastelChart = appConfig.chartColors || {}; + const pastelSeries = pastelChart.series || ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0']; + const pastelSemantic = pastelChart.semantic || {}; const headingColor = appConfig.colors?.headingColor || '#5d596c'; const legendColor = appConfig.colors?.bodyColor || '#6f6b7d'; const fontFamily = appConfig.fontFamily || 'Public Sans'; const chartColors = { - primary: appConfig.colors?.primary || '#7367f0', - success: appConfig.colors?.success || '#28c76f', - warning: appConfig.colors?.warning || '#ff9f43', - info: appConfig.colors?.info || '#00cfe8', - secondary: appConfig.colors?.secondary || '#a8aaae', - labelPrimary: appConfig.colors_label?.primary || 'rgba(115, 103, 240, 0.16)', - labelSuccess: appConfig.colors_label?.success || 'rgba(40, 199, 111, 0.16)', - labelWarning: appConfig.colors_label?.warning || 'rgba(255, 159, 67, 0.16)', - labelInfo: appConfig.colors_label?.info || 'rgba(0, 207, 232, 0.16)', - labelSecondary: appConfig.colors_label?.secondary || 'rgba(168, 170, 174, 0.16)', + primary: pastelSemantic.primary || '#9D8EF0', + success: pastelSemantic.success || '#6FD68F', + warning: pastelSemantic.warning || '#FFB46F', + info: pastelSemantic.info || '#5FC7D6', + secondary: pastelSemantic.secondary || '#9EA6B0', + labelPrimary: 'rgba(111, 214, 143, 0.22)', + labelSuccess: 'rgba(111, 214, 143, 0.24)', + labelWarning: 'rgba(255, 180, 111, 0.22)', + labelInfo: 'rgba(95, 199, 214, 0.20)', + labelSecondary: 'rgba(179, 181, 188, 0.22)', body: appConfig.colors?.bodyColor || '#6f6b7d', muted: appConfig.colors?.textMuted || '#a5a3ae', heading: appConfig.colors?.headingColor || '#5d596c', @@ -1910,12 +1913,7 @@ card: appConfig.colors?.cardColor || '#fff', }; const surveyPalette = [ - '#28a745', - '#007bff', - '#fd7e14', - '#dc3545', - '#6f42c1', - '#20c997', + ...pastelSeries, chartColors.primary, chartColors.info, chartColors.warning, diff --git a/resources/views/dashboard/karakteristik-petani.blade.php b/resources/views/dashboard/karakteristik-petani.blade.php index 27d636c..a9bd4bc 100644 --- a/resources/views/dashboard/karakteristik-petani.blade.php +++ b/resources/views/dashboard/karakteristik-petani.blade.php @@ -6,7 +6,7 @@ .chart-group-header-toggle { cursor: pointer; background-color: rgba(var(--bs-primary-rgb, 115, 103, 240), 0.08); - border-inline-start: 4px solid var(--bs-primary, #7367f0); + border-inline-start: 4px solid var(--bs-primary, #9D8EF0); box-shadow: none; transition: background-color 0.2s ease; } @@ -649,18 +649,18 @@
+ aria-expanded="false" aria-controls="demografi-charts" aria-label="Tampilkan chart">
Profil Demografi

Komposisi petani berdasarkan gender, kelompok umur, dan pendidikan.

-
+
@@ -729,18 +729,18 @@
+ aria-expanded="false" aria-controls="rumah-tangga-charts" aria-label="Tampilkan chart">
Sosial Ekonomi

Tanggungan keluarga, penghasilan usaha tani, dan keanggotaan kelompok tani.

-
+
@@ -816,18 +816,18 @@
+ aria-expanded="false" aria-controls="usaha-tani-charts" aria-label="Tampilkan chart">
Profil Lahan

Karakteristik lahan usaha tani berdasarkan komoditas utama, luas lahan garapan, dan status kepemilikan lahan.

-
+
@@ -1096,18 +1096,18 @@
+ aria-expanded="false" aria-controls="indeks-segmentasi-charts" aria-label="Tampilkan chart">
Indeks Petani

Komposisi petani menurut tingkat kesiapan adopsi, secara keseluruhan dan pada setiap komoditas.

-
+
@@ -1169,18 +1169,18 @@
+ aria-expanded="false" aria-controls="indeks-kesiapan-charts" aria-label="Tampilkan chart">
Dimensi dan Komponen Kesiapan Adopsi

Perbandingan skor dimensi dan komponen kesiapan adopsi petani.

-
+
@@ -1219,18 +1219,18 @@
+ aria-expanded="false" aria-controls="indeks-wilayah-charts" aria-label="Tampilkan chart">
Sebaran wilayah petani

Peta dan komposisi segmen kesiapan adopsi petani per desa/kelurahan.

-
+
@@ -1366,6 +1366,32 @@ document.addEventListener('DOMContentLoaded', function () { const headingColor = config.colors.headingColor; const legendColor = config.colors.bodyColor; const fontFamily = config.fontFamily; + const pastelChart = config.chartColors || {}; + const pastelSeries = pastelChart.series || ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0']; + const pastelSemantic = pastelChart.semantic || {}; + const pastelText = pastelChart.text || {}; + + function hexToRgba(hex, alpha) { + const value = String(hex || '').replace('#', ''); + if (!/^[0-9a-fA-F]{6}$/.test(value)) { + return hex; + } + + const red = parseInt(value.slice(0, 2), 16); + const green = parseInt(value.slice(2, 4), 16); + const blue = parseInt(value.slice(4, 6), 16); + return `rgba(${red}, ${green}, ${blue}, ${alpha})`; + } + + function softBadgeStyle(backgroundColor, textColor) { + const borderColor = hexToRgba(backgroundColor, 0.36); + return [ + `background-color:${hexToRgba(backgroundColor, 0.22)} !important`, + `border:1px solid ${borderColor}`, + `color:${textColor} !important`, + 'font-weight:600' + ].join(';'); + } const farmerDataUrl = @json(route('dashboard.demografi-petani.data')); const farmerProfileUrl = @json(url('/dashboard/profil/petani')); @@ -1500,8 +1526,8 @@ document.addEventListener('DOMContentLoaded', function () { tbody.innerHTML = rows.map((petani, index) => { const rowNumber = ((result.pagination.current_page - 1) * result.pagination.per_page) + index + 1; const genderBadge = Number(petani.jenis_kelamin) === 1 - ? `Laki-laki` - : `Perempuan`; + ? `Laki-laki` + : `Perempuan`; const umur = (petani.umur !== null && petani.umur !== undefined) ? `${petani.umur} tahun` : '-'; return ` @@ -1706,7 +1732,7 @@ document.addEventListener('DOMContentLoaded', function () { }, labels: ['Laki-laki', 'Perempuan'], series: [lakiLaki, perempuan], - colors: [config.colors.info, '#ff5598'], + colors: [pastelSemantic.info || '#5FC7D6', pastelSemantic.pink || '#F47C86'], stroke: { width: 0 }, dataLabels: { enabled: true, @@ -1815,12 +1841,11 @@ document.addEventListener('DOMContentLoaded', function () { data: ageGroupData } ], - colors: ['#c7c2f9', '#9d94f4', '#7367f0', '#5048a8', '#2e2960'], + colors: [pastelSemantic.primary || '#9D8EF0'], plotOptions: { bar: { borderRadius: 4, columnWidth: '45%', - distributed: true, dataLabels: { position: 'top' } } }, @@ -1875,7 +1900,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[dataPointIndex]; const percentage = totalAgeGroup > 0 ? (Number(value) / totalAgeGroup) * 100 : 0; return ` -
+
${label} tahun: ${value} petani (${percentage.toFixed(1)}%)
`; @@ -1918,10 +1943,9 @@ document.addEventListener('DOMContentLoaded', function () { } }, series: [{ name: 'Petani', data: educationData }], - colors: ['#e9f9f0', '#a9e8c5', '#68d79a', '#28c76f', '#1c8b4d', '#104f2c'], + colors: [pastelSemantic.success || '#6FD68F'], plotOptions: { bar: { - distributed: true, horizontal: true, borderRadius: 4, barHeight: '55%', @@ -1975,7 +1999,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[dataPointIndex]; const percentage = totalPendidikan > 0 ? ((value / totalPendidikan) * 100).toFixed(1) : '0.0'; return ` -
+
${label}: ${value} petani (${percentage}%)
`; @@ -2017,10 +2041,9 @@ document.addEventListener('DOMContentLoaded', function () { } }, series: [{ name: 'Petani', data: dependentData }], - colors: ['#ffb7b9', '#ff4c51', '#b23538', '#661e20'], + colors: [pastelSemantic.danger || '#F47C86'], plotOptions: { bar: { - distributed: true, horizontal: true, borderRadius: 4, barHeight: '50%', @@ -2074,7 +2097,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[dataPointIndex]; const percentage = totalDependentsFarmers > 0 ? ((value / totalDependentsFarmers) * 100).toFixed(1) : '0.0'; return ` -
+
${label}: ${value} petani (${percentage}%)
`; @@ -2116,7 +2139,7 @@ document.addEventListener('DOMContentLoaded', function () { }, labels: ['Terdaftar', 'Tidak Terdaftar', 'Belum Terisi'], series: farmerGroupData, - colors: [config.colors.success, config.colors.warning, config.colors.secondary], + colors: [pastelSemantic.success || '#6FD68F', pastelSemantic.warning || '#FFB46F', pastelSemantic.secondary || '#9EA6B0'], stroke: { width: 0 }, dataLabels: { enabled: true, @@ -2215,12 +2238,12 @@ document.addEventListener('DOMContentLoaded', function () { labels: commodityLabels, series: commoditySeries.map(Number), colors: [ - config.colors.primary, - config.colors.warning, - config.colors.success, - config.colors.info, - config.colors.danger, - config.colors.secondary + pastelSemantic.primary || '#9D8EF0', + pastelSemantic.warning || '#FFB46F', + pastelSemantic.success || '#6FD68F', + pastelSemantic.info || '#5FC7D6', + pastelSemantic.danger || '#F47C86', + pastelSemantic.secondary || '#9EA6B0' ], stroke: { width: 0 }, dataLabels: { @@ -2270,7 +2293,7 @@ document.addEventListener('DOMContentLoaded', function () { const percentage = totalCommodityFarmers > 0 ? ((value / totalCommodityFarmers) * 100).toFixed(1) : '0.0'; const color = w.globals.colors[seriesIndex]; return ` -
+
${label}: ${value} petani (${percentage}%)
`; @@ -2335,10 +2358,9 @@ document.addEventListener('DOMContentLoaded', function () { } }, series: [{ name: 'Petani', data: landAreaData }], - colors: ['#ffbb7b', '#ff9f43', '#b26f2e', '#663f1a'], + colors: [pastelSemantic.warning || '#FFB46F'], plotOptions: { bar: { - distributed: true, horizontal: true, borderRadius: 4, barHeight: '50%', @@ -2392,7 +2414,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[dataPointIndex]; const percentage = totalLandFarmers > 0 ? ((value / totalLandFarmers) * 100).toFixed(1) : '0.0'; return ` -
+
${label}: ${value} petani (${percentage}%)
`; @@ -2431,10 +2453,10 @@ document.addEventListener('DOMContentLoaded', function () { labels: landOwnershipStatusLabels, series: landOwnershipStatusSeries, colors: [ - config.colors.success, - config.colors.warning, - config.colors.info, - config.colors.secondary + pastelSemantic.success || '#6FD68F', + pastelSemantic.warning || '#FFB46F', + pastelSemantic.info || '#5FC7D6', + pastelSemantic.secondary || '#9EA6B0' ], stroke: { width: 0 }, dataLabels: { @@ -2538,10 +2560,9 @@ document.addEventListener('DOMContentLoaded', function () { } }, series: [{ name: 'Petani', data: incomeData }], - colors: ['#99e3ec', '#4ccede', '#00bad1', '#008292', '#004a53'], + colors: [pastelSemantic.info || '#5FC7D6'], plotOptions: { bar: { - distributed: true, horizontal: true, borderRadius: 4, barHeight: '50%', @@ -2595,7 +2616,7 @@ document.addEventListener('DOMContentLoaded', function () { const label = w.globals.labels[dataPointIndex]; const percentage = totalIncomeFarmers > 0 ? ((value / totalIncomeFarmers) * 100).toFixed(1) : '0.0'; return ` -
+
${label}: ${value} petani (${percentage}%)
`; @@ -2832,6 +2853,10 @@ document.addEventListener('DOMContentLoaded', function () { -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/dashboard/petani.blade.php b/resources/views/dashboard/petani.blade.php index c6c6e0a..aa717ef 100644 --- a/resources/views/dashboard/petani.blade.php +++ b/resources/views/dashboard/petani.blade.php @@ -493,6 +493,7 @@ $segments = [ legendColor = config.colors.bodyColor; borderColor = config.colors.borderColor; headingColor = config.colors.headingColor; + const pastelSemantic = config.chartColors?.semantic || {}; var radarDimensi = new ApexCharts($("#chart-dimensi")[0], options = { @@ -542,7 +543,7 @@ $segments = [ highlightDataSeries: false } }, - colors: [config.colors.primary, config.colors.info], + colors: [pastelSemantic.primary || '#9D8EF0', pastelSemantic.info || '#5FC7D6'], fill: { opacity: 0.85 }, @@ -640,7 +641,7 @@ $segments = [ highlightDataSeries: false } }, - colors: [config.colors.primary, config.colors.info], + colors: [pastelSemantic.primary || '#9D8EF0', pastelSemantic.info || '#5FC7D6'], fill: { opacity: 0.85 }, diff --git a/resources/views/dashboard/produktivitas.blade.php b/resources/views/dashboard/produktivitas.blade.php index 591d256..ec36a41 100644 --- a/resources/views/dashboard/produktivitas.blade.php +++ b/resources/views/dashboard/produktivitas.blade.php @@ -2,21 +2,85 @@ @push('style')