dashboard produksi & ganti warna chart

This commit is contained in:
2026-10-13 15:55:18 +07:00
parent aef27704f2
commit 80f0056efe
19 changed files with 994 additions and 334 deletions
+13 -9
View File
@@ -238,6 +238,10 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const pastelChart = config.chartColors || {};
const pastelSemantic = pastelChart.semantic || {};
const pastelCategory = pastelChart.categories || {};
const gridColor = pastelChart.grid || '#E8EDF5';
// 1. Inisialisasi Icons
if(typeof lucide !== 'undefined') {
@@ -255,7 +259,7 @@
{
label: 'Desa Mengadopsi (Realisasi)',
data: [0, 3, 8, 15], // Kumulatif: 3 Desa Inti + 12 Baru
backgroundColor: '#8b5cf6', // Purple-500
backgroundColor: pastelCategory.policyPurple || '#9D8EF0',
borderRadius: 4,
order: 2
},
@@ -263,12 +267,12 @@
label: 'Target Perluasan ToC',
type: 'line',
data: [3, 5, 10, 15],
borderColor: '#d946ef', // Fuchsia-500
borderColor: pastelCategory.policyPink || '#F47C86',
backgroundColor: 'transparent',
borderWidth: 3,
borderDash: [5, 5],
tension: 0.3,
pointBackgroundColor: '#d946ef',
pointBackgroundColor: pastelCategory.policyPink || '#F47C86',
order: 1
}
]
@@ -280,7 +284,7 @@
scales: {
y: {
beginAtZero: true,
grid: { color: '#f3f4f6' },
grid: { color: gridColor },
ticks: { stepSize: 5 }
},
x: { grid: { display: false } }
@@ -309,10 +313,10 @@
datasets: [{
data: [4, 2, 1, 1],
backgroundColor: [
'#f59e0b', // Amber-500
'#10b981', // Emerald-500
'#3b82f6', // Blue-500
'#8b5cf6' // Purple-500
pastelSemantic.warning || '#FFB46F',
pastelSemantic.success || '#6FD68F',
pastelSemantic.info || '#5FC7D6',
pastelCategory.policyPurple || '#9D8EF0'
],
borderWidth: 2,
borderColor: '#ffffff',
@@ -335,4 +339,4 @@
});
</script>
@endpush
@endpush
+27 -27
View File
@@ -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
<tr>
+14 -10
View File
@@ -208,6 +208,10 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const pastelChart = config.chartColors || {};
const pastelSemantic = pastelChart.semantic || {};
const pastelCategory = pastelChart.categories || {};
const gridColor = pastelChart.grid || '#E8EDF5';
// 1. Inisialisasi Icons
if(typeof lucide !== 'undefined') {
@@ -225,7 +229,7 @@
{
label: 'Mentah/Gelondong (Rp)',
data: [35000, 200000, 25000, 15000],
backgroundColor: '#9ca3af', // Gray
backgroundColor: pastelSemantic.gray || '#9EA6B0',
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 },
barPercentage: 0.6,
categoryPercentage: 0.8
@@ -233,7 +237,7 @@
{
label: 'Produk Olahan (Rp)',
data: [120000, 1500000, 80000, 45000],
backgroundColor: '#d97706', // Amber-600
backgroundColor: pastelSemantic.warning || '#FFB46F',
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 },
barPercentage: 0.6,
categoryPercentage: 0.8
@@ -246,7 +250,7 @@
scales: {
y: {
beginAtZero: true,
grid: { color: '#f3f4f6' },
grid: { color: gridColor },
ticks: {
callback: function(value) {
if (value >= 1000000) return 'Rp ' + (value/1000000) + ' Jt';
@@ -282,8 +286,8 @@
{
label: 'Kopi Bubuk/Roasted',
data: [12, 15, 18, 25, 30, 45],
borderColor: '#6F4E37',
backgroundColor: 'rgba(111, 78, 55, 0.1)',
borderColor: pastelCategory.coffee || '#6FD68F',
backgroundColor: 'rgba(104, 193, 124, 0.16)',
fill: true,
tension: 0.4,
borderWidth: 2
@@ -291,7 +295,7 @@
{
label: 'Kakao Fermentasi',
data: [5, 8, 12, 15, 22, 28],
borderColor: '#D2691E',
borderColor: pastelCategory.cacao || '#9D8EF0',
backgroundColor: 'transparent',
tension: 0.4,
borderWidth: 2
@@ -303,9 +307,9 @@
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: {
y: {
title: { display: true, text: 'Volume (Ton)' },
grid: { color: '#f3f4f6' }
y: {
title: { display: true, text: 'Volume (Ton)' },
grid: { color: gridColor }
},
x: { grid: { display: false } }
},
@@ -318,4 +322,4 @@
});
</script>
@endpush
@endpush
@@ -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,
@@ -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 @@
<div class="col-12">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#demografi-charts"
aria-expanded="true" aria-controls="demografi-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="demografi-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Profil Demografi</h5>
<p class="text-body-secondary mb-0">Komposisi petani berdasarkan gender, kelompok umur, dan pendidikan.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="demografi-charts">
<div class="collapse chart-group-panel" id="demografi-charts">
<div class="row g-6 pt-4">
<div class="col-12 col-md-6 col-xl-4">
<div class="card h-100">
@@ -729,18 +729,18 @@
<div class="col-12">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#rumah-tangga-charts"
aria-expanded="true" aria-controls="rumah-tangga-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="rumah-tangga-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Sosial Ekonomi</h5>
<p class="text-body-secondary mb-0">Tanggungan keluarga, penghasilan usaha tani, dan keanggotaan kelompok tani.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="rumah-tangga-charts">
<div class="collapse chart-group-panel" id="rumah-tangga-charts">
<div class="row g-6 pt-4">
<div class="col-12 col-md-6 col-xl-4">
<div class="card h-100">
@@ -816,18 +816,18 @@
<div class="col-12">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#usaha-tani-charts"
aria-expanded="true" aria-controls="usaha-tani-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="usaha-tani-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Profil Lahan</h5>
<p class="text-body-secondary mb-0">Karakteristik lahan usaha tani berdasarkan komoditas utama, luas lahan garapan, dan status kepemilikan lahan.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="usaha-tani-charts">
<div class="collapse chart-group-panel" id="usaha-tani-charts">
<div class="row g-6 pt-4">
<div class="col-12 col-md-6 col-xl-4">
<div class="card h-100">
@@ -1096,18 +1096,18 @@
<div class="py-4">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#indeks-segmentasi-charts"
aria-expanded="true" aria-controls="indeks-segmentasi-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="indeks-segmentasi-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Indeks Petani</h5>
<p class="text-body-secondary mb-0">Komposisi petani menurut tingkat kesiapan adopsi, secara keseluruhan dan pada setiap komoditas.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="indeks-segmentasi-charts">
<div class="collapse chart-group-panel" id="indeks-segmentasi-charts">
<div class="row g-6 py-4">
<div class="col-lg-6 col-sm-12">
<div class="card card-border-shadow-primary h-100">
@@ -1169,18 +1169,18 @@
<div class="py-4">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#indeks-kesiapan-charts"
aria-expanded="true" aria-controls="indeks-kesiapan-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="indeks-kesiapan-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Dimensi dan Komponen Kesiapan Adopsi</h5>
<p class="text-body-secondary mb-0">Perbandingan skor dimensi dan komponen kesiapan adopsi petani.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="indeks-kesiapan-charts">
<div class="collapse chart-group-panel" id="indeks-kesiapan-charts">
<div class="row g-6 py-4">
<div class="col-lg-6 col-sm-12">
<div class="card card-border-shadow-info h-100">
@@ -1219,18 +1219,18 @@
<div class="py-4">
<div class="card chart-group-header-toggle" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#indeks-wilayah-charts"
aria-expanded="true" aria-controls="indeks-wilayah-charts" aria-label="Sembunyikan chart">
aria-expanded="false" aria-controls="indeks-wilayah-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Sebaran wilayah petani</h5>
<p class="text-body-secondary mb-0">Peta dan komposisi segmen kesiapan adopsi petani per desa/kelurahan.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-up"></i>
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse show chart-group-panel" id="indeks-wilayah-charts">
<div class="collapse chart-group-panel" id="indeks-wilayah-charts">
<div class="row g-6 py-4">
<div class="col-lg-12 col-sm-12">
<div class="card card-border-shadow-danger h-100">
@@ -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
? `<span class="badge bg-label-info">Laki-laki</span>`
: `<span class="badge" style="background-color:#ffe8f1 !important; color:#ff5598 !important;">Perempuan</span>`;
? `<span class="badge" style="${softBadgeStyle(pastelSemantic.info || '#5FC7D6', pastelText.info || '#00BAD1')}">Laki-laki</span>`
: `<span class="badge" style="${softBadgeStyle(pastelSemantic.danger || '#F47C86', pastelText.danger || '#FF4C51')}">Perempuan</span>`;
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 `
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label} tahun: <strong>${value} petani (${percentage.toFixed(1)}%)</strong>
</div>
`;
@@ -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 `
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label}: <strong>${value} petani (${percentage}%)</strong>
</div>
`;
@@ -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 `
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label}: <strong>${value} petani (${percentage}%)</strong>
</div>
`;
@@ -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 `
<div style="background:${color}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${color}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label}: <strong>${value} petani (${percentage}%)</strong>
</div>
`;
@@ -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 `
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label}: <strong>${value} petani (${percentage}%)</strong>
</div>
`;
@@ -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 `
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:#fff; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
<div style="background:${w.config.colors[dataPointIndex % w.config.colors.length]}; color:${headingColor}; padding:8px 12px; border-radius:5px; font-size:13px; font-weight:500; font-family:${fontFamily}; box-shadow:0 4px 12px rgba(0,0,0,.15);">
${label}: <strong>${value} petani (${percentage}%)</strong>
</div>
`;
@@ -2832,6 +2853,10 @@ document.addEventListener('DOMContentLoaded', function () {
<script>
$(document).ready(function () {
const segments = @json($segments);
const segmentPalette = segments.map(function (segment, index) {
return config.chartColors?.semantic?.[segment.color] || config.chartColors?.series?.[index % config.chartColors.series.length] || '#9EA6B0';
});
const segmentTextColors = config.chartColors?.text || {};
// Chart Segmentasi Petani
var chartSegmentasiPetani;
@@ -2871,7 +2896,7 @@ document.addEventListener('DOMContentLoaded', function () {
}
},
labels: @json(collect($segments)->pluck('name')->values()),
colors: @json($colors),
colors: segmentPalette,
plotOptions: {
pie: {
borderRadius: 12,
@@ -2925,16 +2950,17 @@ document.addEventListener('DOMContentLoaded', function () {
const total = response.series[index] ?? 0;
const persen = response.persentase[index] ?? 0;
const segmentColor = segmentTextColors[segment.color] || config.colors[segment.color] || segmentPalette[index] || '#808390';
html += `
<tr>
<td>
<div class="d-flex justify-content-start align-items-center gap-2">
<div class="flex-shrink-0">
<i class="icon-base ti ${segment.icon} icon-lg text-${segment.color}"></i>
<i class="icon-base ti ${segment.icon} icon-lg" style="color:${segmentColor};"></i>
</div>
<span class="text-${segment.color}">
<span style="color:${segmentColor};">
${segment.name}
</span>
</div>
@@ -2971,9 +2997,12 @@ document.addEventListener('DOMContentLoaded', function () {
</script>
<script>
$(document).ready(function () {
const segmentPalette = (@json($segments)).map(function (segment, index) {
return config.chartColors?.semantic?.[segment.color] || config.chartColors?.series?.[index % config.chartColors.series.length] || '#9EA6B0';
});
var optionsSegmentasiPetaniByKomoditas = {
series: [],
colors: @json($colors),
colors: segmentPalette,
chart: {
type: 'bar',
@@ -3157,7 +3186,7 @@ document.addEventListener('DOMContentLoaded', function () {
plotOptions: { radar: { polygons: { strokeColors: borderColor, connectorColors: borderColor } } },
stroke: { width: 2 },
legend: { show: false },
colors: [config.colors.primary],
colors: [config.chartColors?.semantic?.primary || '#9D8EF0'],
fill: { opacity: 0.12 },
markers: {
size: 5, strokeWidth: 2, strokeColors: config.colors.cardColor, hover: { sizeOffset: 2 },
@@ -3208,7 +3237,14 @@ document.addEventListener('DOMContentLoaded', function () {
</script>
<script>
$(document).ready(function () {
var palette = [config.colors.success, config.colors.primary, config.colors.warning, config.colors.info, config.colors.danger, config.colors.secondary];
var palette = [
config.chartColors?.semantic?.success || '#6FD68F',
config.chartColors?.semantic?.primary || '#9D8EF0',
config.chartColors?.semantic?.warning || '#FFB46F',
config.chartColors?.semantic?.info || '#5FC7D6',
config.chartColors?.semantic?.danger || '#F47C86',
config.chartColors?.semantic?.secondary || '#9EA6B0'
];
[
{ id: 'dimensi', key: 'radarDimensi', url: "{{ route('dashboard.indeks-petani.dimensi') }}", title: "Dimensi Kesiapan Adopsi", labels: ["Sistem & Praktik Budidaya","Sosial Ekonomi Rumah Tangga","Rantai Nilai & Akses Pasar"], categories: [["Sistem & Praktik","Budidaya"],["Sosial Ekonomi","Rumah Tangga"],["Rantai Nilai &","Akses Pasar"]] },
{ id: 'komponen', key: 'radarKomponen', url: "{{ route('dashboard.indeks-petani.komponen') }}", title: "Komponen Kesiapan Adopsi", labels: ["Motivasi & Kapasitas Adopsi","Kondisi Ekonomi Rumah Tangga","Praktik Budidaya & GAP","Keterlibatan Kelembagaan","Akses & Kebebasan Keuangan","Rantai Nilai & Akses Pasar"], categories: [["Motivasi & Kapasitas","Adopsi"],["Kondisi Ekonomi","Rumah Tangga"],["Praktik Budidaya","& GAP"],["Keterlibatan","Kelembagaan"],["Akses & Kebebasan","Keuangan"],["Rantai Nilai &","Akses Pasar"]] }
@@ -3282,7 +3318,7 @@ document.addEventListener('DOMContentLoaded', function () {
const warnaSegmen = {
@foreach($segments as $segment)
"{{ $segment['name'] }}": "var(--bs-{{ $segment['color'] }})",
"{{ $segment['name'] }}": config.chartColors?.semantic?.["{{ $segment['color'] }}"] || '#9EA6B0',
@endforeach
};
@@ -3340,7 +3376,7 @@ document.addEventListener('DOMContentLoaded', function () {
color: '#666',
weight: 1,
fillColor:
warnaSegmen[desa.segmentasi?.segmen] || '#ccc',
warnaSegmen[desa.segmentasi?.segmen] || '#9EA6B0',
fillOpacity: 0.7
});
@@ -3389,6 +3425,7 @@ document.addEventListener('DOMContentLoaded', function () {
</script>
<script>
$(document).ready(function () {
const segmentTextColors = config.chartColors?.text || {};
var tableDesa = $('#table-desa').DataTable({
"paging": true,
"pageLength": 5,
@@ -3429,7 +3466,8 @@ document.addEventListener('DOMContentLoaded', function () {
var count = Number(data && data.count) || 0;
if (type !== 'display') return count;
var percentage = Number(data && data.percentage || 0).toLocaleString('id-ID', { maximumFractionDigits: 2 });
return '<span class="fw-semibold text-' + (data && data.color || 'secondary') + '">' + count.toLocaleString('id-ID') +
var segmentColor = segmentTextColors[data && data.color || 'secondary'] || '#808390';
return '<span class="fw-semibold" style="color:' + segmentColor + ';">' + count.toLocaleString('id-ID') +
'</span> <span class="text-body-secondary small">(' + percentage + '%)</span>';
}
};
@@ -3439,6 +3477,36 @@ document.addEventListener('DOMContentLoaded', function () {
</script>
<script>
$(document).ready(function () {
const indexSegmentColorKeys = {
@foreach($segments as $segment)
"{{ $segment['name'] }}": "{{ $segment['color'] }}",
@endforeach
};
const indexSegmentColors = {
@foreach($segments as $segment)
"{{ $segment['name'] }}": config.chartColors?.semantic?.["{{ $segment['color'] }}"] || '#9EA6B0',
@endforeach
};
const indexSegmentTextColors = config.chartColors?.text || {};
function indexHexToRgba(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 indexSegmentBadgeStyle(backgroundColor, textColor) {
return [
`background-color:${indexHexToRgba(backgroundColor, 0.22)} !important`,
`border:1px solid ${indexHexToRgba(backgroundColor, 0.36)}`,
`color:${textColor} !important`,
'font-weight:600'
].join(';');
}
var tablePetani = $('#table-petani').DataTable({
dom: 'rt<"card-footer d-flex flex-wrap justify-content-between align-items-center gap-3"ip>',
pagingType: 'full_numbers',
@@ -3540,9 +3608,12 @@ document.addEventListener('DOMContentLoaded', function () {
if (!data.skor || !data.segmen) {
return '';
}
const segmentKey = indexSegmentColorKeys[data.segmen] || data.color || 'secondary';
const segmentColor = indexSegmentColors[data.segmen] || config.chartColors?.semantic?.[segmentKey] || '#9EA6B0';
const segmentTextColor = indexSegmentTextColors[segmentKey] || '#808390';
return `
<span class="badge bg-${data.color ?? 'secondary'}">
<span class="badge" style="${indexSegmentBadgeStyle(segmentColor, segmentTextColor)}">
${data.segmen}
</span>
`;
@@ -212,6 +212,9 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const pastelChart = config.chartColors || {};
const pastelCategory = pastelChart.categories || {};
const gridColor = pastelChart.grid || '#E8EDF5';
// 1. Inisialisasi Icons
if(typeof lucide !== 'undefined') {
@@ -230,8 +233,8 @@
label: 'Total Produksi Lahan (Ton)',
type: 'line',
data: [100, 110, 125, 145],
borderColor: '#10b981', // Emerald-500
backgroundColor: '#10b981',
borderColor: pastelCategory.marketGreen || '#6FD68F',
backgroundColor: pastelCategory.marketGreen || '#6FD68F',
borderWidth: 3,
tension: 0.3,
fill: false
@@ -239,14 +242,14 @@
{
label: 'Tengkulak / Pasar Bebas',
data: [80, 65, 40, 25],
backgroundColor: '#ef4444', // Red-500
backgroundColor: pastelCategory.marketRed || '#F47C86',
stack: 'Stack 0',
borderRadius: { topLeft: 0, topRight: 0, bottomLeft: 4, bottomRight: 4 }
},
{
label: 'Kelembagaan (Koperasi/BUMDes)',
data: [20, 45, 85, 120],
backgroundColor: '#3b82f6', // Blue-500
backgroundColor: pastelCategory.marketBlue || '#5FC7D6',
stack: 'Stack 0',
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 }
}
@@ -260,7 +263,7 @@
y: {
stacked: true,
beginAtZero: true,
grid: { color: '#f3f4f6' }
grid: { color: gridColor }
},
x: {
stacked: true,
@@ -292,20 +295,20 @@
{
label: 'Kopi & Kakao',
data: [150, 280, 320, 500, 650, 850],
borderColor: '#6F4E37',
borderColor: pastelCategory.coffee || '#6FD68F',
backgroundColor: 'transparent',
tension: 0.4,
borderWidth: 2,
pointBackgroundColor: '#6F4E37'
pointBackgroundColor: pastelCategory.coffee || '#6FD68F'
},
{
label: 'Vanili & Kemiri',
data: [50, 120, 210, 350, 420, 600],
borderColor: '#eab308',
borderColor: pastelCategory.vanilla || '#FFB46F',
backgroundColor: 'transparent',
tension: 0.4,
borderWidth: 2,
pointBackgroundColor: '#eab308'
pointBackgroundColor: pastelCategory.vanilla || '#FFB46F'
}
]
},
@@ -316,7 +319,7 @@
scales: {
y: {
title: { display: false },
grid: { color: '#f3f4f6' },
grid: { color: gridColor },
ticks: {
callback: function(value) {
return 'Rp ' + value + ' Jt';
@@ -341,4 +344,4 @@
});
</script>
@endpush
@endpush
+3 -2
View File
@@ -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
},
+408 -65
View File
@@ -2,21 +2,85 @@
@push('style')
<style>
.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, #9D8EF0);
box-shadow: none;
transition: background-color 0.2s ease;
}
.chart-group-header-toggle:hover {
background-color: rgba(var(--bs-primary-rgb, 115, 103, 240), 0.13);
}
.chart-group-header-toggle .card-body {
padding: 12px 16px;
}
.chart-group-header-toggle h5 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 2px;
}
.chart-group-header-toggle p {
font-size: 0.8125rem;
line-height: 1.4;
}
.chart-group-header-toggle:focus-visible {
outline: 2px solid var(--bs-primary);
outline-offset: 3px;
}
.chart-group-icon-toggle {
width: 34px;
height: 34px;
flex-shrink: 0;
}
.chart-group-icon-toggle .icon-base {
font-size: 24px;
width: 24px;
height: 24px;
}
.chart-group-panel.show,
.chart-group-panel.collapsing {
margin-bottom: 1.5rem;
}
.produksi-kpi-value {
font-size: 1.35rem;
font-weight: 800;
line-height: 1.1;
color: var(--bs-heading-color);
font-size: 1.6rem;
font-weight: 600;
line-height: 1.2;
overflow-wrap: anywhere;
}
.produksi-kpi-label {
font-size: .82rem;
font-weight: 700;
margin-bottom: .2rem;
color: var(--bs-heading-color);
font-size: 0.9375rem;
font-weight: 400;
}
.produksi-kpi-desc {
font-size: .78rem;
font-size: 0.8125rem;
line-height: 1.4;
}
.produksi-chart-card .card-title h5 {
color: var(--bs-heading-color);
font-size: 1.125rem;
font-weight: 500;
line-height: 1.35;
letter-spacing: 0;
}
.produksi-chart-card .card-title small {
font-size: 0.8125rem;
line-height: 1.4;
}
.produksi-chart-sm {
@@ -31,6 +95,52 @@
min-height: 240px;
}
.produksi-praktik-chart {
min-height: 360px;
}
.produksi-pie-canvas {
min-height: 190px;
}
.produksi-pie-legend {
display: flex;
flex-direction: column;
gap: .5rem;
margin-top: .75rem;
}
.produksi-pie-legend-item {
display: flex;
align-items: flex-start;
gap: .5rem;
font-size: 0.8125rem;
line-height: 1.4;
color: var(--bs-body-color);
}
.produksi-pie-legend-marker {
width: .65rem;
height: .65rem;
border-radius: 999px;
flex: 0 0 .65rem;
margin-top: .24rem;
}
.produksi-pie-legend-label {
min-width: 0;
flex: 1 1 auto;
overflow-wrap: anywhere;
}
.produksi-pie-legend-value {
flex: 0 0 auto;
color: var(--bs-heading-color);
font-size: 0.875rem;
font-weight: 600;
white-space: nowrap;
}
.produksi-empty {
min-height: 220px;
display: flex;
@@ -142,7 +252,7 @@
<div class="row g-4 mb-4">
@foreach ($kpiCards as $card)
<div class="col-xl-4 col-md-4">
<div class="card card-border-shadow-{{ $card['variant'] }} h-100">
<div class="card card-border-shadow-{{ $card['variant'] }} h-100 produksi-kpi-card">
<div class="card-body">
<div class="d-flex align-items-center gap-4">
<div class="avatar avatar-lg">
@@ -159,9 +269,9 @@
@endif
</div>
<p class="mb-0 mt-1">{{ $card['label'] }}</p>
<p class="mb-0 mt-1 produksi-kpi-label">{{ $card['label'] }}</p>
<small class="text-body-secondary">{{ $card['description'] }}</small>
<small class="text-body-secondary produksi-kpi-desc">{{ $card['description'] }}</small>
</div>
</div>
</div>
@@ -170,33 +280,29 @@
@endforeach
</div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-start gap-2 mb-3">
<div>
<h5 class="mb-1">Kondisi Kebun</h5>
<p class="text-body-secondary mb-0">Ringkasan umur lahan, umur tanaman, kondisi tanaman, gejala terlihat, dan tingkat keparahan serangan.</p>
<div class="card chart-group-header-toggle mb-4" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#produksi-charts"
aria-expanded="false" aria-controls="produksi-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Lahan dan Tanaman</h5>
<p class="text-body-secondary mb-0">Distribusi umur tanaman, jumlah tanaman per komoditas, kondisi tanaman, gejala serangan, dan tingkat keparahan serangan.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
<button type="button" class="btn btn-label-secondary btn-sm chart-group-toggle" data-bs-toggle="collapse" data-bs-target="#produksi-charts" aria-expanded="true" aria-controls="produksi-charts">Sembunyikan Chart</button>
</div>
<div class="collapse show chart-group-panel" id="produksi-charts">
<div class="collapse chart-group-panel" id="produksi-charts">
<div class="row g-4 mb-4">
<div class="col-xl-4 col-lg-6">
<div class="card h-100">
<div class="card-header">
<h6 class="mb-0 fw-bold">Umur Lahan</h6>
<small class="text-body-secondary">Kelompok umur lahan terdata</small>
</div>
<div class="card-body">
<div id="chart-umur-lahan" class="produksi-chart-sm"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100">
<div class="card-header">
<h6 class="mb-0 fw-bold">Umur Tanaman</h6>
<small class="text-body-secondary">Kelompok umur tanaman saat ini</small>
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Umur Tanaman</h5>
<small class="text-body-secondary">Kelompok umur tanaman saat ini</small>
</div>
</div>
<div class="card-body">
<div id="chart-umur-tanaman" class="produksi-chart-sm"></div>
@@ -205,10 +311,26 @@
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100">
<div class="card-header">
<h6 class="mb-0 fw-bold">Kondisi Tanaman</h6>
<small class="text-body-secondary">Distribusi kondisi baik, sedang, buruk, atau mati</small>
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Jumlah Tanaman Per Komoditas</h5>
<small class="text-body-secondary">Jumlah tanaman terdata pada setiap komoditas</small>
</div>
</div>
<div class="card-body">
<div id="chart-jumlah-tanaman-komoditas" class="produksi-chart-sm"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Kondisi Tanaman</h5>
<small class="text-body-secondary">Distribusi kondisi baik, sedang, buruk, atau mati</small>
</div>
</div>
<div class="card-body">
<div id="chart-kondisi-tanaman" class="produksi-chart"></div>
@@ -219,10 +341,12 @@
<div class="row g-4">
<div class="col-xl-8 col-lg-8">
<div class="card h-100">
<div class="card-header">
<h6 class="mb-0 fw-bold">Gejala Serangan yang Terlihat</h6>
<small class="text-body-secondary">Gabungan data hama, penyakit, dan gejala otomatis</small>
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Gejala Serangan yang Terlihat</h5>
<small class="text-body-secondary">Gabungan data hama, penyakit, dan gejala otomatis</small>
</div>
</div>
<div class="card-body">
<div id="chart-gejala-serangan" class="produksi-chart-gejala"></div>
@@ -231,10 +355,12 @@
</div>
<div class="col-xl-4 col-lg-4">
<div class="card h-100">
<div class="card-header">
<h6 class="mb-0 fw-bold">Keparahan Serangan</h6>
<small class="text-body-secondary">Ringan, sedang, dan parah</small>
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Keparahan Serangan</h5>
<small class="text-body-secondary">Ringan, sedang, dan parah</small>
</div>
</div>
<div class="card-body">
<div id="chart-keparahan-serangan" class="produksi-chart"></div>
@@ -243,6 +369,111 @@
</div>
</div>
</div>
<div class="card chart-group-header-toggle mb-4" role="button" tabindex="0"
data-bs-toggle="collapse" data-bs-target="#praktik-budidaya-charts"
aria-expanded="false" aria-controls="praktik-budidaya-charts" aria-label="Tampilkan chart">
<div class="card-body d-flex justify-content-between align-items-center gap-3">
<div>
<h5>Praktik Budidaya</h5>
<p class="text-body-secondary mb-0">Distribusi intensitas pemangkasan, pemupukan, penanganan hama dan penyakit, pembersihan lahan, sumber air, dan kebiasaan praktik baik.</p>
</div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
</div>
<div class="collapse chart-group-panel" id="praktik-budidaya-charts">
<div class="row g-4 mb-4">
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Intensitas Pemangkasan</h5>
<small class="text-body-secondary">Frekuensi dan ketepatan teknik pemangkasan</small>
</div>
</div>
<div class="card-body">
<div id="chart-intensitas-pemangkasan" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Intensitas Pemupukan</h5>
<small class="text-body-secondary">Kesesuaian jenis, dosis, dan waktu pemupukan</small>
</div>
</div>
<div class="card-body">
<div id="chart-intensitas-pemupukan" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Metode Penanganan Hama dan Penyakit</h5>
<small class="text-body-secondary">Cara penanganan serangan yang dilakukan petani</small>
</div>
</div>
<div class="card-body">
<div id="chart-metode-penanganan-hama-penyakit" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Intensitas Pembersihan Lahan</h5>
<small class="text-body-secondary">Kebiasaan sanitasi dan pembersihan kebun</small>
</div>
</div>
<div class="card-body">
<div id="chart-intensitas-pembersihan-lahan" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Sumber Air untuk Kebun</h5>
<small class="text-body-secondary">Sumber air yang digunakan di kebun</small>
</div>
</div>
<div class="card-body">
<div id="chart-sumber-air-kebun" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 produksi-chart-card">
<div class="card-header d-flex align-items-start justify-content-between gap-2">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Kebiasaan Praktik Baik</h5>
<small class="text-body-secondary">Praktik konservasi sederhana yang dilakukan petani</small>
</div>
</div>
<div class="card-body">
<div id="chart-kebiasaan-praktik-baik" class="produksi-praktik-chart"></div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@@ -251,9 +482,14 @@
document.addEventListener('DOMContentLoaded', function () {
const chartData = @json($chartData);
const wilayah = @json($wilayah);
const chartColors = ['#28a745', '#007bff', '#fd7e14', '#dc3545', '#6f42c1', '#20c997', '#6c757d', '#17a2b8'];
const labelColor = '#6c757d';
const borderColor = 'rgba(128, 128, 128, .18)';
const headingColor = config.colors.headingColor;
const legendColor = config.colors.bodyColor;
const fontFamily = config.fontFamily;
const pastelChart = config.chartColors || {};
const pastelSemantic = pastelChart.semantic || {};
const chartColors = pastelChart.series || ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0'];
const labelColor = legendColor;
const borderColor = pastelChart.grid || 'rgba(128, 128, 128, .18)';
function hasData(data) {
return data && Array.isArray(data.series) && data.series.some(function (value) {
@@ -285,7 +521,7 @@ document.addEventListener('DOMContentLoaded', function () {
}, 0);
new ApexCharts(element, {
chart: { type: 'donut', height: 265, toolbar: { show: false } },
chart: { type: 'donut', height: 265, fontFamily: fontFamily, toolbar: { show: false } },
labels: data.labels,
series: data.series.map(Number),
colors: chartColors,
@@ -296,10 +532,19 @@ document.addEventListener('DOMContentLoaded', function () {
formatter: function (value) {
return value.toFixed(0) + '%';
},
style: {
fontSize: '13px',
fontFamily: fontFamily,
fontWeight: 600,
colors: ['#fff'],
},
},
legend: {
position: 'bottom',
labels: { colors: labelColor },
fontSize: '13px',
fontFamily: fontFamily,
fontWeight: 400,
formatter: function (seriesName, options) {
const value = Number(options.w.globals.series[options.seriesIndex] || 0);
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
@@ -307,6 +552,7 @@ document.addEventListener('DOMContentLoaded', function () {
},
},
tooltip: {
theme: 'dark',
y: {
formatter: function (value) {
const percentage = total > 0 ? ((Number(value) / total) * 100).toFixed(1) : '0.0';
@@ -334,6 +580,76 @@ document.addEventListener('DOMContentLoaded', function () {
}).render();
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function renderPieWithLegend(selector, data, options = {}) {
const element = document.querySelector(selector);
if (!element) return;
if (!hasData(data)) return emptyChart(selector);
const series = data.series.map(Number);
const total = series.reduce(function (sum, value) {
return sum + Number(value || 0);
}, 0);
const colors = options.colors || chartColors;
element.innerHTML = '<div class="produksi-pie-canvas"></div><div class="produksi-pie-legend"></div>';
const canvas = element.querySelector('.produksi-pie-canvas');
const legend = element.querySelector('.produksi-pie-legend');
legend.innerHTML = data.labels.map(function (label, index) {
const value = Number(series[index] || 0);
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
const color = colors[index % colors.length];
return `
<div class="produksi-pie-legend-item">
<span class="produksi-pie-legend-marker" style="background-color: ${escapeHtml(color)}"></span>
<span class="produksi-pie-legend-label">${escapeHtml(label)}</span>
<span class="produksi-pie-legend-value">${formatNumber(value)} (${percentage}%)</span>
</div>
`;
}).join('');
new ApexCharts(canvas, {
chart: { type: 'pie', height: options.height || 205, fontFamily: fontFamily, toolbar: { show: false } },
labels: data.labels,
series: series,
colors: colors,
stroke: { width: 0 },
legend: { show: false },
dataLabels: {
enabled: true,
formatter: function (value) {
return value >= 8 ? value.toFixed(0) + '%' : '';
},
style: {
fontSize: '11px',
fontFamily: fontFamily,
fontWeight: 600,
colors: ['#fff'],
},
},
tooltip: {
theme: 'dark',
y: {
formatter: function (value) {
const percentage = total > 0 ? ((Number(value) / total) * 100).toFixed(1) : '0.0';
return `${formatNumber(value)} ${options.unit || 'data'} (${percentage}%)`;
},
},
},
plotOptions: { pie: { expandOnClick: true } },
}).render();
}
function renderBar(selector, data, options = {}) {
const element = document.querySelector(selector);
if (!element) return;
@@ -345,11 +661,11 @@ document.addEventListener('DOMContentLoaded', function () {
}, 0);
new ApexCharts(element, {
chart: { type: 'bar', height: options.height || 285, toolbar: { show: false } },
chart: { type: 'bar', height: options.height || 285, fontFamily: fontFamily, toolbar: { show: false } },
series: [{ name: options.name || 'Jumlah', data: data.series.map(Number) }],
xaxis: {
categories: data.labels,
labels: { style: { colors: labelColor } },
labels: { style: { colors: labelColor, fontSize: '12px', fontFamily: fontFamily } },
axisBorder: { color: borderColor },
axisTicks: { color: borderColor },
},
@@ -357,10 +673,10 @@ document.addEventListener('DOMContentLoaded', function () {
labels: {
minWidth: options.yLabelWidth || 0,
maxWidth: options.yLabelWidth || 160,
style: { colors: labelColor },
style: { colors: labelColor, fontSize: '12px', fontFamily: fontFamily },
},
},
colors: [options.color || '#28a745'],
colors: [options.color || pastelSemantic.success || '#6FD68F'],
plotOptions: {
bar: {
horizontal: horizontal,
@@ -377,8 +693,9 @@ document.addEventListener('DOMContentLoaded', function () {
offsetY: horizontal ? 0 : -20,
style: {
fontSize: '12px',
fontFamily: fontFamily,
fontWeight: 500,
colors: [labelColor],
colors: [headingColor],
},
formatter: function (value) {
const percentage = total > 0 ? ((Number(value) / total) * 100).toFixed(1) : '0.0';
@@ -394,6 +711,7 @@ document.addEventListener('DOMContentLoaded', function () {
},
},
tooltip: {
theme: 'dark',
y: {
formatter: function (value) {
const percentage = total > 0 ? ((Number(value) / total) * 100).toFixed(1) : '0.0';
@@ -439,11 +757,17 @@ document.addEventListener('DOMContentLoaded', function () {
element.innerHTML = '<div class="produksi-empty">ApexCharts belum dimuat.</div>';
});
} else {
renderBar('#chart-umur-lahan', chartData.umurLahan, { color: '#007bff', unit: 'lahan' });
renderBar('#chart-umur-tanaman', chartData.umurTanaman, { color: '#fd7e14', unit: 'data' });
renderBar('#chart-umur-tanaman', chartData.umurTanaman, { color: pastelSemantic.warning || '#FFB46F', unit: 'data' });
renderBar('#chart-jumlah-tanaman-komoditas', chartData.jumlahTanamanPerKomoditas, { color: pastelSemantic.success || '#6FD68F', unit: 'tanaman' });
renderDonut('#chart-kondisi-tanaman', chartData.kondisiTanaman);
renderBar('#chart-gejala-serangan', chartData.gejalaSerangan, { horizontal: true, height: 250, color: '#dc3545', unit: 'data', yLabelWidth: 210, rightPadding: 115 });
renderBar('#chart-gejala-serangan', chartData.gejalaSerangan, { horizontal: true, height: 250, color: pastelSemantic.danger || '#F47C86', unit: 'data', yLabelWidth: 210, rightPadding: 115 });
renderDonut('#chart-keparahan-serangan', chartData.keparahanSerangan);
renderPieWithLegend('#chart-intensitas-pemangkasan', chartData.intensitasPemangkasan, { unit: 'petani' });
renderPieWithLegend('#chart-intensitas-pemupukan', chartData.intensitasPemupukan, { unit: 'petani' });
renderPieWithLegend('#chart-metode-penanganan-hama-penyakit', chartData.metodePenangananHamaPenyakit, { unit: 'petani' });
renderPieWithLegend('#chart-intensitas-pembersihan-lahan', chartData.intensitasPembersihanLahan, { unit: 'petani' });
renderPieWithLegend('#chart-sumber-air-kebun', chartData.sumberAirKebun, { unit: 'petani' });
renderPieWithLegend('#chart-kebiasaan-praktik-baik', chartData.kebiasaanPraktikBaik, { unit: 'petani' });
}
$('#filter-provinsi, #filter-kabkota, #filter-kecamatan, #filter-desa, #filter-komoditas, #filter-segmen').select2({ width: '100%' });
@@ -493,16 +817,35 @@ document.addEventListener('DOMContentLoaded', function () {
window.location.href = @json(route('dashboard.produktivitas'));
});
document.querySelectorAll('.chart-group-panel').forEach(function (panel) {
const button = document.querySelector('[aria-controls="' + panel.id + '"]');
panel.addEventListener('hidden.bs.collapse', function (event) {
if (event.target !== panel || !button) return;
button.textContent = 'Tampilkan Chart';
document.querySelectorAll('.chart-group-header-toggle').forEach(function (header) {
header.addEventListener('keydown', function (event) {
if (event.target !== header || !['Enter', ' '].includes(event.key)) return;
event.preventDefault();
header.click();
});
panel.addEventListener('shown.bs.collapse', function (event) {
if (event.target !== panel || !button) return;
button.textContent = 'Sembunyikan Chart';
window.dispatchEvent(new Event('resize'));
});
document.querySelectorAll('.chart-group-panel').forEach(function (panel) {
const control = document.querySelector('.chart-group-header-toggle[aria-controls="' + panel.id + '"]');
if (!control) return;
const icon = control.querySelector('.chart-group-icon-toggle i');
const syncState = function () {
const expanded = panel.classList.contains('show');
const label = expanded ? 'Sembunyikan chart' : 'Tampilkan chart';
control.setAttribute('aria-expanded', String(expanded));
control.setAttribute('aria-label', label);
control.title = label;
icon.classList.toggle('tabler-chevron-up', expanded);
icon.classList.toggle('tabler-chevron-down', !expanded);
};
syncState();
['hidden.bs.collapse', 'shown.bs.collapse'].forEach(function (eventName) {
panel.addEventListener(eventName, function (event) {
if (event.target !== panel) return;
syncState();
if (eventName === 'shown.bs.collapse') window.dispatchEvent(new Event('resize'));
});
});
});
});
+28 -25
View File
@@ -144,10 +144,10 @@
<div class="absolute bottom-3 left-3 bg-white/95 p-3 rounded-lg text-xs border border-gray-200 shadow-sm backdrop-blur-sm">
<div class="font-bold text-gray-700 mb-2">Legenda Tanaman:</div>
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#6F4E37] rounded-full shadow-sm"></div> Kopi</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#eab308] rounded-full shadow-sm"></div> Vanili</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#D2691E] rounded-full shadow-sm"></div> Kakao</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9ca3af] rounded-full shadow-sm"></div> Kemiri</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#6FD68F] rounded-full shadow-sm"></div> Kopi</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#FFB46F] rounded-full shadow-sm"></div> Vanili</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9D8EF0] rounded-full shadow-sm"></div> Kakao</div>
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9EA6B0] rounded-full shadow-sm"></div> Kemiri</div>
</div>
</div>
</div>
@@ -253,6 +253,9 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const pastelChart = config.chartColors || {};
const pastelCategory = pastelChart.categories || {};
const gridColor = pastelChart.grid || '#E8EDF5';
// 1. Inisialisasi Icons
if(typeof lucide !== 'undefined') {
@@ -263,19 +266,19 @@ document.addEventListener('DOMContentLoaded', function() {
const spatialData = [
{
id: 'L-001', owner: 'Yosef Neta', familySize: 5, area: '1.2 Ha', village: 'Radabata',
polygon: "15,25 75,15 90,85 20,70", fill: "rgba(34, 197, 94, 0.15)", stroke: "#16a34a",
polygon: "15,25 75,15 90,85 20,70", fill: "rgba(111, 214, 143, 0.24)", stroke: "#6FD68F",
plants: [
{ id: 'P-01', type: 'Kopi', cx: 35, cy: 35, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemupukan Organik (Okt 2023)', panen: '45 kg/musim', color: '#6F4E37' },
{ id: 'P-02', type: 'Vanili', cx: 65, cy: 45, planted: 'Jan 2022', status: 'Berbunga', intervensi: 'Pelatihan Polinasi (Feb 2024)', panen: 'Belum panen', color: '#eab308' },
{ id: 'P-03', type: 'Kopi', cx: 50, cy: 65, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemangkasan (Nov 2023)', panen: '40 kg/musim', color: '#6F4E37' },
{ id: 'P-01', type: 'Kopi', cx: 35, cy: 35, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemupukan Organik (Okt 2023)', panen: '45 kg/musim', color: pastelCategory.coffee || '#6FD68F' },
{ id: 'P-02', type: 'Vanili', cx: 65, cy: 45, planted: 'Jan 2022', status: 'Berbunga', intervensi: 'Pelatihan Polinasi (Feb 2024)', panen: 'Belum panen', color: pastelCategory.vanilla || '#FFB46F' },
{ id: 'P-03', type: 'Kopi', cx: 50, cy: 65, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemangkasan (Nov 2023)', panen: '40 kg/musim', color: pastelCategory.coffee || '#6FD68F' },
]
},
{
id: 'L-002', owner: 'Maria Goreti', familySize: 3, area: '0.8 Ha', village: 'Ubedolumolo',
polygon: "110,40 170,15 190,65 125,85", fill: "rgba(59, 130, 246, 0.15)", stroke: "#2563eb",
polygon: "110,40 170,15 190,65 125,85", fill: "rgba(95, 199, 214, 0.20)", stroke: "#5FC7D6",
plants: [
{ id: 'P-04', type: 'Kemiri', cx: 140, cy: 45, planted: 'Agu 2018', status: 'Produktif', intervensi: 'Pembersihan Gulma', panen: '120 kg/musim', color: '#9ca3af' },
{ id: 'P-05', type: 'Kakao', cx: 165, cy: 55, planted: 'Okt 2020', status: 'Terserang Hama', intervensi: 'Penyemprotan Bio-pestisida (Jan 2024)', panen: '20 kg/musim', color: '#D2691E' },
{ id: 'P-04', type: 'Kemiri', cx: 140, cy: 45, planted: 'Agu 2018', status: 'Produktif', intervensi: 'Pembersihan Gulma', panen: '120 kg/musim', color: pastelCategory.candlenut || '#9EA6B0' },
{ id: 'P-05', type: 'Kakao', cx: 165, cy: 55, planted: 'Okt 2020', status: 'Terserang Hama', intervensi: 'Penyemprotan Bio-pestisida (Jan 2024)', panen: '20 kg/musim', color: pastelCategory.cacao || '#9D8EF0' },
]
}
];
@@ -440,24 +443,24 @@ document.addEventListener('DOMContentLoaded', function() {
{
label: 'Kopi',
data: [0.5, 0.7, 1.1, 1.5],
borderColor: '#6F4E37',
backgroundColor: '#6F4E37',
borderColor: pastelCategory.coffee || '#6FD68F',
backgroundColor: pastelCategory.coffee || '#6FD68F',
tension: 0.3,
borderWidth: 2
},
{
label: 'Vanili',
data: [0.1, 0.15, 0.3, 0.5],
borderColor: '#eab308',
backgroundColor: '#eab308',
borderColor: pastelCategory.vanilla || '#FFB46F',
backgroundColor: pastelCategory.vanilla || '#FFB46F',
tension: 0.3,
borderWidth: 2
},
{
label: 'Kakao',
data: [0.4, 0.5, 0.8, 1.2],
borderColor: '#D2691E',
backgroundColor: '#D2691E',
borderColor: pastelCategory.cacao || '#9D8EF0',
backgroundColor: pastelCategory.cacao || '#9D8EF0',
tension: 0.3,
borderWidth: 2
}
@@ -467,7 +470,7 @@ document.addEventListener('DOMContentLoaded', function() {
responsive: true, maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: {
y: { title: { display: true, text: 'Ton per Hektar (Ton/Ha)' }, grid: { color: '#f3f4f6' } },
y: { title: { display: true, text: 'Ton per Hektar (Ton/Ha)' }, grid: { color: gridColor } },
x: { grid: { display: false } }
},
plugins: {
@@ -489,11 +492,11 @@ document.addEventListener('DOMContentLoaded', function() {
label: 'Jumlah Bibit Didistribusikan',
data: [15000, 5200, 8000, 7000, 10000],
backgroundColor: [
'rgba(111, 78, 55, 0.8)', // Kopi
'rgba(234, 179, 8, 0.8)', // Vanili
'rgba(210, 105, 30, 0.8)', // Kakao
'rgba(156, 163, 175, 0.8)', // Kemiri
'rgba(34, 197, 94, 0.8)' // Pelindung (Hijau)
'rgba(111, 214, 143, 0.86)', // Kopi
'rgba(255, 180, 111, 0.86)', // Vanili
'rgba(157, 142, 240, 0.86)', // Kakao
'rgba(179, 181, 188, 0.86)', // Kemiri
'rgba(101, 216, 189, 0.86)' // Pelindung
],
borderRadius: 4
}
@@ -502,7 +505,7 @@ document.addEventListener('DOMContentLoaded', function() {
options: {
responsive: true, maintainAspectRatio: false,
scales: {
y: { beginAtZero: true, grid: { color: '#f3f4f6' } },
y: { beginAtZero: true, grid: { color: gridColor } },
x: { grid: { display: false } }
},
plugins: {
@@ -515,4 +518,4 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
@endpush
@endpush
+26 -17
View File
@@ -219,6 +219,10 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const pastelChart = config.chartColors || {};
const pastelSemantic = pastelChart.semantic || {};
const pastelCategory = pastelChart.categories || {};
const gridColor = pastelChart.grid || '#E8EDF5';
// Inisialisasi Icons (Khusus Mockup, Vuexy mungkin pakai library icon sendiri seperti Tabler/FontAwesome)
if(typeof lucide !== 'undefined') {
@@ -233,23 +237,23 @@
data: {
labels: ['2021', '2022', '2023', '2024'],
datasets: [
{
label: 'Baseline (Tanpa Intervensi)',
data: [12, 12.5, 13, 13.5],
borderColor: '#9ca3af',
backgroundColor: 'transparent',
{
label: 'Baseline (Tanpa Intervensi)',
data: [12, 12.5, 13, 13.5],
borderColor: pastelSemantic.gray || '#9EA6B0',
backgroundColor: 'transparent',
borderDash: [5, 5],
fill: false,
tension: 0.4
fill: false,
tension: 0.4
},
{
label: 'Pendapatan Aktual',
data: [12, 15, 22, 31],
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.15)', // Light green fill
fill: true,
{
label: 'Pendapatan Aktual',
data: [12, 15, 22, 31],
borderColor: pastelCategory.marketGreen || '#6FD68F',
backgroundColor: 'rgba(111, 214, 143, 0.20)',
fill: true,
tension: 0.4,
pointBackgroundColor: '#10b981',
pointBackgroundColor: pastelCategory.marketGreen || '#6FD68F',
pointBorderWidth: 2,
pointRadius: 4
}
@@ -265,7 +269,7 @@
scales: {
y: {
beginAtZero: false,
grid: { color: '#f3f4f6' },
grid: { color: gridColor },
ticks: { callback: function(val) { return 'Rp ' + val + 'M'; } }
},
x: {
@@ -296,7 +300,12 @@
labels: ['Kopi', 'Vanili', 'Kakao', 'Kemiri'],
datasets: [{
data: [45, 25, 15, 15],
backgroundColor: ['#6F4E37', '#eab308', '#D2691E', '#9ca3af'],
backgroundColor: [
pastelCategory.coffee || '#6FD68F',
pastelCategory.vanilla || '#FFB46F',
pastelCategory.cacao || '#9D8EF0',
pastelCategory.candlenut || '#9EA6B0'
],
borderWidth: 2,
borderColor: '#ffffff',
hoverOffset: 4
@@ -324,4 +333,4 @@
}
});
</script>
@endpush
@endpush
@@ -12,9 +12,9 @@
box-shadow: 0 .25rem 1rem rgba(34, 48, 62, .08);
}
.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;
@@ -444,10 +444,10 @@
@forelse ($progressPetani as $petani)
@php
$barColor = match (true) {
$petani['percent'] === 100 => '#28a745',
$petani['percent'] >= 50 => '#007bff',
$petani['percent'] >= 20 => '#fd7e14',
default => '#dc3545',
$petani['percent'] === 100 => '#6FD68F',
$petani['percent'] >= 50 => '#5FC7D6',
$petani['percent'] >= 20 => '#FFB46F',
default => '#F47C86',
};
$initial = collect(explode(' ', trim($petani['name'])))
@@ -465,8 +465,8 @@
style="
width: 28px;
height: 28px;
background: rgba(40,167,69,.15);
color: #28a745;
background: rgba(111,214,143,.24);
color: #6FD68F;
"
>
{{ $initial ?: 'PT' }}
@@ -1054,12 +1054,13 @@ document.addEventListener('DOMContentLoaded', function () {
|--------------------------------------------------------------------------
*/
const primaryColor = config.colors.primary;
const secondaryColor = config.colors.secondary;
const successColor = config.colors.success;
const infoColor = config.colors.info;
const warningColor = config.colors.warning;
const dangerColor = config.colors.danger;
const pastelSemantic = config.chartColors?.semantic || {};
const primaryColor = pastelSemantic.primary || '#9D8EF0';
const secondaryColor = pastelSemantic.secondary || '#9EA6B0';
const successColor = pastelSemantic.success || '#6FD68F';
const infoColor = pastelSemantic.info || '#5FC7D6';
const warningColor = pastelSemantic.warning || '#FFB46F';
const dangerColor = pastelSemantic.danger || '#F47C86';
const bodyColor = config.colors.bodyColor;
const borderColor = config.colors.borderColor;
@@ -1346,4 +1347,4 @@ document.addEventListener('DOMContentLoaded', function () {
});
</script>
@endpush
@endpush