1332 lines
52 KiB
PHP
1332 lines
52 KiB
PHP
@extends('layout.main')
|
|
|
|
@push('style')
|
|
<style>
|
|
.indicator-page-header {
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
|
|
.indicator-page-title {
|
|
color: var(--bs-heading-color);
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
line-height: 1.25;
|
|
margin-bottom: .35rem;
|
|
}
|
|
|
|
.indicator-filter-bar {
|
|
align-items: end;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: .75rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
|
|
.indicator-filter-group {
|
|
flex: 1 1 150px;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.indicator-filter-actions {
|
|
display: flex;
|
|
flex: 999 1 240px;
|
|
gap: .75rem;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.indicator-filter-primary-actions {
|
|
display: flex;
|
|
gap: .75rem;
|
|
}
|
|
|
|
.indicator-filter-label {
|
|
color: var(--bs-body-color);
|
|
font-size: .75rem;
|
|
font-weight: 600;
|
|
margin-bottom: .3rem;
|
|
}
|
|
|
|
.indicator-filter-bar .form-select,
|
|
.indicator-filter-bar .btn {
|
|
min-height: 38px;
|
|
}
|
|
|
|
.indicator-filter-bar .form-select {
|
|
background-color: var(--bs-white);
|
|
border-color: var(--bs-border-color);
|
|
border-radius: var(--bs-border-radius);
|
|
color: var(--bs-body-color);
|
|
font-size: .8125rem;
|
|
padding-bottom: .375rem;
|
|
padding-top: .375rem;
|
|
}
|
|
|
|
.indicator-filter-bar .btn {
|
|
font-size: .8125rem;
|
|
padding-bottom: .375rem;
|
|
padding-top: .375rem;
|
|
}
|
|
|
|
.indicator-filter-bar .form-select:focus {
|
|
border-color: rgba(40, 167, 69, .55);
|
|
box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12);
|
|
}
|
|
|
|
.indicator-filter-static {
|
|
align-items: center;
|
|
background-color: var(--bs-white);
|
|
border: 1px solid var(--bs-border-color);
|
|
border-radius: var(--bs-border-radius);
|
|
color: var(--bs-body-color);
|
|
display: flex;
|
|
font-size: .8125rem;
|
|
min-height: 38px;
|
|
padding: .375rem .75rem;
|
|
}
|
|
|
|
.indicator-filter-bar .input-group-text {
|
|
background: var(--bs-white);
|
|
border-color: var(--bs-border-color);
|
|
color: var(--bs-secondary-color);
|
|
padding-left: .625rem;
|
|
padding-right: .625rem;
|
|
}
|
|
|
|
.indicator-export-btn {
|
|
border-color: #28a745;
|
|
color: #28a745;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.indicator-export-btn:hover,
|
|
.indicator-export-btn:focus {
|
|
background: rgba(40, 167, 69, .08);
|
|
border-color: #28a745;
|
|
color: #28a745;
|
|
}
|
|
|
|
.indicator-summary-value {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.indicator-tabs {
|
|
gap: .5rem;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.indicator-tabs::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.indicator-tabs .nav-item {
|
|
flex: 1 0 auto;
|
|
}
|
|
|
|
.indicator-tabs .nav-link {
|
|
align-items: center;
|
|
display: flex;
|
|
justify-content: center;
|
|
min-height: 38px;
|
|
white-space: nowrap;
|
|
width: 100%;
|
|
}
|
|
|
|
.indicator-tab-content {
|
|
background: transparent;
|
|
box-shadow: none;
|
|
padding: 0;
|
|
}
|
|
|
|
#chart-petani-komoditas .apexcharts-tooltip,
|
|
#chart-luas-kecamatan .apexcharts-tooltip,
|
|
#chart-kondisi-tanaman .apexcharts-tooltip,
|
|
#chart-status-kepemilikan-lahan .apexcharts-tooltip,
|
|
#chart-serangan-hama .apexcharts-tooltip,
|
|
#chart-penyakit-tanaman .apexcharts-tooltip,
|
|
#chart-keparahan-serangan .apexcharts-tooltip,
|
|
#chart-status-hak-kelola-lahan .apexcharts-tooltip,
|
|
#chart-praktik-pemupukan .apexcharts-tooltip,
|
|
#chart-produksi-komoditas .apexcharts-tooltip,
|
|
#chart-produktivitas-komoditas .apexcharts-tooltip,
|
|
#chart-luas-panen-komoditas .apexcharts-tooltip,
|
|
#chart-status-panen .apexcharts-tooltip,
|
|
#chart-survey-produktivitas-ha .apexcharts-tooltip,
|
|
#chart-survey-produktivitas-pohon .apexcharts-tooltip {
|
|
background: transparent !important;
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
.indicator-chart {
|
|
min-height: 330px;
|
|
}
|
|
|
|
.indicator-chart-sm {
|
|
min-height: 330px;
|
|
}
|
|
|
|
@media (max-width: 767.98px) {
|
|
.indicator-filter-bar,
|
|
.indicator-filter-actions,
|
|
.indicator-filter-primary-actions {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
width: 100%;
|
|
}
|
|
|
|
.indicator-filter-group {
|
|
min-width: 100%;
|
|
}
|
|
|
|
.indicator-filter-bar .btn {
|
|
justify-content: center;
|
|
width: 100%;
|
|
}
|
|
|
|
.indicator-tabs .nav-item {
|
|
flex-basis: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="container-xxl flex-grow-1 container-p-y">
|
|
<div class="indicator-page-header">
|
|
<h4 class="indicator-page-title">
|
|
Indikator Pertanian
|
|
</h4>
|
|
<p class="text-muted mb-0">
|
|
Kondisi dan performa usaha tani petani berdasarkan hasil survei
|
|
</p>
|
|
</div>
|
|
|
|
<div class="indicator-filter-bar" aria-label="Filter dashboard indikator pertanian">
|
|
@foreach ($filterOptions as $filter)
|
|
<div class="indicator-filter-group">
|
|
<label for="filter-{{ $filter['key'] }}" class="indicator-filter-label">
|
|
{{ $filter['label'] }}
|
|
</label>
|
|
|
|
@if (!empty($filter['icon']))
|
|
<div class="input-group">
|
|
<span class="input-group-text" aria-hidden="true">
|
|
<i class="ti {{ $filter['icon'] }}"></i>
|
|
</span>
|
|
@if (!empty($filter['readonly']))
|
|
@php
|
|
$selectedOption = collect($filter['options'])
|
|
->firstWhere('value', $initialFilters[$filter['key']]);
|
|
@endphp
|
|
|
|
<div
|
|
id="filter-{{ $filter['key'] }}"
|
|
class="form-control indicator-filter-static"
|
|
data-filter-key="{{ $filter['key'] }}"
|
|
data-filter-value="{{ $initialFilters[$filter['key']] }}"
|
|
aria-label="Filter {{ $filter['label'] }}"
|
|
role="text"
|
|
>
|
|
{{ $selectedOption['label'] ?? 'Ngada' }}
|
|
</div>
|
|
@else
|
|
<select
|
|
id="filter-{{ $filter['key'] }}"
|
|
class="form-select indicator-filter-select"
|
|
data-filter-key="{{ $filter['key'] }}"
|
|
aria-label="Filter {{ $filter['label'] }}"
|
|
>
|
|
@foreach ($filter['options'] as $option)
|
|
<option
|
|
value="{{ $option['value'] }}"
|
|
@selected($initialFilters[$filter['key']] === $option['value'])
|
|
>
|
|
{{ $option['label'] }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
</div>
|
|
@else
|
|
<select
|
|
id="filter-{{ $filter['key'] }}"
|
|
class="form-select indicator-filter-select"
|
|
data-filter-key="{{ $filter['key'] }}"
|
|
aria-label="Filter {{ $filter['label'] }}"
|
|
@disabled($filter['key'] === 'desa' && $initialFilters['kecamatan'] === 'all')
|
|
>
|
|
@foreach ($filter['options'] as $option)
|
|
<option
|
|
value="{{ $option['value'] }}"
|
|
@if (isset($option['parent'])) data-parent="{{ $option['parent'] }}" @endif
|
|
@selected($initialFilters[$filter['key']] === $option['value'])
|
|
>
|
|
{{ $option['label'] }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
|
|
<div class="indicator-filter-actions">
|
|
<div class="indicator-filter-primary-actions">
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-secondary"
|
|
id="indicator-reset-filter"
|
|
aria-label="Reset filter indikator pertanian"
|
|
>
|
|
<i class="ti tabler-rotate-clockwise-2 me-1"></i>
|
|
Reset Filter
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-success indicator-export-btn"
|
|
id="indicator-export-data"
|
|
aria-label="Export data indikator pertanian"
|
|
>
|
|
<i class="ti tabler-download me-1"></i>
|
|
Export Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-6">
|
|
@foreach ($summaryData as $summary)
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="card card-border-shadow-{{ $summary['variant'] }} h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="avatar me-4">
|
|
<span class="avatar-initial rounded bg-label-{{ $summary['variant'] }}">
|
|
<i class="icon-base ti {{ $summary['icon'] }} icon-28px"></i>
|
|
</span>
|
|
</div>
|
|
|
|
<h4 class="mb-0 indicator-summary-value">
|
|
{{ $summary['value'] }}
|
|
</h4>
|
|
</div>
|
|
|
|
<p class="mb-1">
|
|
{{ $summary['label'] }}
|
|
</p>
|
|
|
|
<p class="mb-0">
|
|
<small class="text-body-secondary">
|
|
{{ $summary['description'] }}
|
|
</small>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<div class="nav-align-top mt-4">
|
|
<ul class="nav nav-pills flex-column flex-md-row mb-6 row-gap-2 indicator-tabs" role="tablist">
|
|
<li class="nav-item">
|
|
<button
|
|
type="button"
|
|
class="nav-link active"
|
|
role="tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#indicator-tab-lahan-tanaman"
|
|
aria-controls="indicator-tab-lahan-tanaman"
|
|
aria-selected="true"
|
|
>
|
|
<i class="icon-base ti tabler-trees icon-sm me-1_5"></i>
|
|
Lahan dan Tanaman
|
|
</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button
|
|
type="button"
|
|
class="nav-link"
|
|
role="tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#indicator-tab-produksi"
|
|
aria-controls="indicator-tab-produksi"
|
|
aria-selected="false"
|
|
>
|
|
<i class="icon-base ti tabler-chart-histogram icon-sm me-1_5"></i>
|
|
Produksi dan Produktivitas
|
|
</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button
|
|
type="button"
|
|
class="nav-link"
|
|
role="tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#indicator-tab-input-produksi"
|
|
aria-controls="indicator-tab-input-produksi"
|
|
aria-selected="false"
|
|
>
|
|
<i class="icon-base ti tabler-package icon-sm me-1_5"></i>
|
|
Input Produksi
|
|
</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button
|
|
type="button"
|
|
class="nav-link"
|
|
role="tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#indicator-tab-panen"
|
|
aria-controls="indicator-tab-panen"
|
|
aria-selected="false"
|
|
>
|
|
<i class="icon-base ti tabler-basket icon-sm me-1_5"></i>
|
|
Panen dan Pascapanen
|
|
</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button
|
|
type="button"
|
|
class="nav-link"
|
|
role="tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#indicator-tab-akses-pasar"
|
|
aria-controls="indicator-tab-akses-pasar"
|
|
aria-selected="false"
|
|
>
|
|
<i class="icon-base ti tabler-building-store icon-sm me-1_5"></i>
|
|
Akses Pasar
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content indicator-tab-content">
|
|
<div
|
|
class="tab-pane fade show active"
|
|
id="indicator-tab-lahan-tanaman"
|
|
role="tabpanel"
|
|
>
|
|
<div class="row g-6 mt-1">
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Distribusi Komoditas</h5>
|
|
<small class="text-body-secondary">Jumlah petani per komoditas</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-petani-komoditas" class="indicator-chart-sm"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Luas Lahan per Kecamatan</h5>
|
|
<small class="text-body-secondary">Kecamatan dengan luas lahan terdata terbesar</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-luas-kecamatan" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Hak Kelola Lahan</h5>
|
|
<small class="text-body-secondary">Berdasarkan survei sosial ekonomi</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-status-hak-kelola-lahan" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Status Kepemilikan Lahan</h5>
|
|
<small class="text-body-secondary">Berdasarkan data lahan terdata</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-status-kepemilikan-lahan" class="indicator-chart-sm"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Serangan Hama</h5>
|
|
<small class="text-body-secondary">Jenis hama pada tanaman/lahan</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-serangan-hama" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Keparahan Serangan</h5>
|
|
<small class="text-body-secondary">Tingkat dampak hama/penyakit</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-keparahan-serangan" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Kondisi Tanaman</h5>
|
|
<small class="text-body-secondary">Berdasarkan tanaman/lahan terdata</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-kondisi-tanaman" class="indicator-chart-sm"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Penyakit Tanaman</h5>
|
|
<small class="text-body-secondary">Jenis penyakit pada tanaman/lahan</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-penyakit-tanaman" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Praktik Pemupukan</h5>
|
|
<small class="text-body-secondary">Berdasarkan survei praktik budidaya</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-praktik-pemupukan" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="indicator-tab-produksi" role="tabpanel">
|
|
<div class="row g-6 mt-1">
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Total Produksi per Komoditas</h5>
|
|
<small class="text-body-secondary">Akumulasi hasil panen aktual</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-produksi-komoditas" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Produktivitas Aktual</h5>
|
|
<small class="text-body-secondary">Rata-rata kg/ha per komoditas</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-produktivitas-komoditas" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Luas Panen per Komoditas</h5>
|
|
<small class="text-body-secondary">Akumulasi luas panen terdata</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-luas-panen-komoditas" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Status Panen</h5>
|
|
<small class="text-body-secondary">Jumlah catatan produksi per status</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-status-panen" class="indicator-chart-sm"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Produktivitas Survey (ha/tahun)</h5>
|
|
<small class="text-body-secondary">Rata-rata produktivitas per komoditas</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-survey-produktivitas-ha" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xxl-4 col-lg-6">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="card-title mb-0">
|
|
<h5 class="m-0 me-2">Produktivitas Survey (pohon/tahun)</h5>
|
|
<small class="text-body-secondary">Rata-rata produktivitas per komoditas</small>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="chart-survey-produktivitas-pohon" class="indicator-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@foreach ([
|
|
'indicator-tab-input-produksi' => 'Input Produksi',
|
|
'indicator-tab-panen' => 'Panen dan Pascapanen',
|
|
'indicator-tab-akses-pasar' => 'Akses Pasar',
|
|
] as $tabId => $tabLabel)
|
|
|
|
<div class="tab-pane fade" id="{{ $tabId }}" role="tabpanel">
|
|
<div class="card">
|
|
<div class="card-body text-center text-body-secondary py-5">
|
|
Belum ada grafik untuk {{ $tabLabel }}.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const chartData = @json($chartData);
|
|
const dashboardUrl = @json(route('dashboard.indikator-pertanian'));
|
|
const initialFilters = @json($initialFilters);
|
|
const filters = { ...initialFilters };
|
|
const filterSelects = document.querySelectorAll('.indicator-filter-select');
|
|
const kecamatanSelect = document.querySelector('[data-filter-key="kecamatan"]');
|
|
const desaSelect = document.querySelector('[data-filter-key="desa"]');
|
|
const desaOptions = desaSelect
|
|
? Array.from(desaSelect.options).map(function (option) {
|
|
return {
|
|
value: option.value,
|
|
label: option.textContent,
|
|
parent: option.dataset.parent || null,
|
|
};
|
|
})
|
|
: [];
|
|
const resetButton = document.getElementById('indicator-reset-filter');
|
|
const exportButton = document.getElementById('indicator-export-data');
|
|
|
|
function applyFilters() {
|
|
const params = new URLSearchParams();
|
|
|
|
['kecamatan', 'desa', 'komoditas', 'segmentasi'].forEach(function (key) {
|
|
if (filters[key] && filters[key] !== 'all') {
|
|
params.set(key, filters[key]);
|
|
}
|
|
});
|
|
|
|
window.location.href = params.toString()
|
|
? dashboardUrl + '?' + params.toString()
|
|
: dashboardUrl;
|
|
}
|
|
|
|
function renderDesaOptions(selectedValue = initialFilters.desa) {
|
|
if (!desaSelect || !kecamatanSelect) {
|
|
return;
|
|
}
|
|
|
|
const selectedKecamatan = kecamatanSelect.value;
|
|
const canSelectDesa = selectedKecamatan !== 'all';
|
|
const availableOptions = desaOptions.filter(function (option) {
|
|
return option.value === 'all' || option.parent === selectedKecamatan;
|
|
});
|
|
|
|
desaSelect.innerHTML = '';
|
|
|
|
availableOptions.forEach(function (option) {
|
|
const element = new Option(option.label, option.value);
|
|
|
|
if (option.parent) {
|
|
element.dataset.parent = option.parent;
|
|
}
|
|
|
|
desaSelect.add(element);
|
|
});
|
|
|
|
desaSelect.disabled = !canSelectDesa;
|
|
desaSelect.value = availableOptions.some(function (option) {
|
|
return option.value === selectedValue;
|
|
}) ? selectedValue : 'all';
|
|
filters.desa = desaSelect.value;
|
|
}
|
|
|
|
renderDesaOptions();
|
|
|
|
filterSelects.forEach(function (select) {
|
|
select.addEventListener('change', function (event) {
|
|
filters[event.target.dataset.filterKey] = event.target.value;
|
|
|
|
if (event.target.dataset.filterKey === 'kecamatan') {
|
|
renderDesaOptions('all');
|
|
}
|
|
|
|
applyFilters();
|
|
});
|
|
});
|
|
|
|
resetButton?.addEventListener('click', function () {
|
|
window.location.href = dashboardUrl;
|
|
});
|
|
|
|
exportButton?.addEventListener('click', function () {});
|
|
|
|
if (typeof ApexCharts === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const appConfig = typeof config !== 'undefined' ? config : {};
|
|
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)',
|
|
body: appConfig.colors?.bodyColor || '#6f6b7d',
|
|
muted: appConfig.colors?.textMuted || '#a5a3ae',
|
|
heading: appConfig.colors?.headingColor || '#5d596c',
|
|
border: appConfig.colors?.borderColor || '#dbdade',
|
|
card: appConfig.colors?.cardColor || '#fff',
|
|
};
|
|
const surveyPalette = [
|
|
'#28a745',
|
|
'#007bff',
|
|
'#fd7e14',
|
|
'#dc3545',
|
|
'#6f42c1',
|
|
'#20c997',
|
|
chartColors.primary,
|
|
chartColors.info,
|
|
chartColors.warning,
|
|
];
|
|
const shuffleColors = function (colors) {
|
|
const shuffled = [...colors];
|
|
|
|
for (let index = shuffled.length - 1; index > 0; index -= 1) {
|
|
const randomIndex = Math.floor(Math.random() * (index + 1));
|
|
[shuffled[index], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[index]];
|
|
}
|
|
|
|
return shuffled;
|
|
};
|
|
const colorSet = shuffleColors(surveyPalette);
|
|
const colorsFor = function (offset = 0) {
|
|
return [
|
|
...colorSet.slice(offset),
|
|
...colorSet.slice(0, offset),
|
|
];
|
|
};
|
|
|
|
function hasSeries(data) {
|
|
return Array.isArray(data?.series)
|
|
&& data.series.length > 0
|
|
&& data.series.some(function (value) {
|
|
return Number(value) > 0;
|
|
});
|
|
}
|
|
|
|
function formatNumber(value, digits = 0) {
|
|
return Number(value || 0).toLocaleString('id-ID', {
|
|
maximumFractionDigits: digits,
|
|
minimumFractionDigits: digits,
|
|
});
|
|
}
|
|
|
|
function wrapAxisLabel(value, maxCharacters = 13) {
|
|
const words = String(value || '').split(/\s+/).filter(Boolean);
|
|
const lines = [];
|
|
let currentLine = '';
|
|
|
|
words.forEach(function (word) {
|
|
const nextLine = currentLine ? currentLine + ' ' + word : word;
|
|
|
|
if (currentLine && nextLine.length > maxCharacters) {
|
|
lines.push(currentLine);
|
|
currentLine = word;
|
|
return;
|
|
}
|
|
|
|
currentLine = nextLine;
|
|
});
|
|
|
|
if (currentLine) {
|
|
lines.push(currentLine);
|
|
}
|
|
|
|
return lines.length > 1 ? lines : (lines[0] || '');
|
|
}
|
|
|
|
function renderDonut(selector, data, colors, options = {}) {
|
|
const element = document.querySelector(selector);
|
|
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
if (!hasSeries(data)) {
|
|
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
|
return;
|
|
}
|
|
|
|
const total = data.series.reduce(function (sum, value) {
|
|
return sum + Number(value || 0);
|
|
}, 0);
|
|
const unit = options.unit || '';
|
|
const valueDigits = options.valueDigits || 0;
|
|
|
|
new ApexCharts(element, {
|
|
chart: {
|
|
type: 'donut',
|
|
height: options.height || 330,
|
|
parentHeightOffset: 0,
|
|
toolbar: { show: false },
|
|
},
|
|
labels: data.labels,
|
|
series: data.series,
|
|
colors: colors,
|
|
stroke: { width: 0 },
|
|
dataLabels: { enabled: false },
|
|
legend: {
|
|
show: true,
|
|
position: 'bottom',
|
|
offsetY: 10,
|
|
fontSize: '13px',
|
|
fontFamily: fontFamily,
|
|
fontWeight: 400,
|
|
labels: {
|
|
colors: legendColor,
|
|
useSeriesColors: false,
|
|
},
|
|
markers: {
|
|
size: 4,
|
|
strokeWidth: 0,
|
|
},
|
|
itemMargin: {
|
|
horizontal: 12,
|
|
vertical: 5,
|
|
},
|
|
formatter: function (seriesName, chartOptions) {
|
|
const value = Number(chartOptions.w.globals.series[chartOptions.seriesIndex] || 0);
|
|
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
|
|
|
|
return `${seriesName}: ${formatNumber(value, valueDigits)}${unit ? ' ' + unit : ''} (${percentage}%)`;
|
|
},
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
followCursor: true,
|
|
custom: function ({ series, seriesIndex, w }) {
|
|
const value = Number(series[seriesIndex] || 0);
|
|
const label = w.globals.labels[seriesIndex];
|
|
const percentage = total > 0 ? ((value / total) * 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);
|
|
"
|
|
>
|
|
${label}:
|
|
<strong>
|
|
${formatNumber(value, valueDigits)}${unit ? ' ' + unit : ''}
|
|
(${percentage}%)
|
|
</strong>
|
|
</div>
|
|
`;
|
|
},
|
|
},
|
|
plotOptions: {
|
|
pie: {
|
|
donut: {
|
|
size: options.donutSize || '68%',
|
|
labels: {
|
|
show: true,
|
|
value: {
|
|
fontSize: options.valueFontSize || '30px',
|
|
fontFamily: fontFamily,
|
|
color: headingColor,
|
|
fontWeight: 500,
|
|
offsetY: -15,
|
|
formatter: function (value) {
|
|
return formatNumber(value, valueDigits);
|
|
},
|
|
},
|
|
name: {
|
|
offsetY: 25,
|
|
fontFamily: fontFamily,
|
|
},
|
|
total: {
|
|
show: true,
|
|
showAlways: true,
|
|
fontSize: '13px',
|
|
fontFamily: fontFamily,
|
|
color: legendColor,
|
|
label: options.totalLabel || 'Total',
|
|
formatter: function () {
|
|
return formatNumber(total, valueDigits);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responsive: [
|
|
{
|
|
breakpoint: 1400,
|
|
options: {
|
|
chart: { height: 350 },
|
|
legend: { position: 'bottom' },
|
|
},
|
|
},
|
|
{
|
|
breakpoint: 576,
|
|
options: {
|
|
chart: { height: 330 },
|
|
legend: { position: 'bottom' },
|
|
},
|
|
},
|
|
],
|
|
states: {
|
|
hover: {
|
|
filter: {
|
|
type: 'lighten',
|
|
value: 0.08,
|
|
},
|
|
},
|
|
active: {
|
|
filter: {
|
|
type: 'darken',
|
|
value: 0.08,
|
|
},
|
|
},
|
|
},
|
|
}).render();
|
|
}
|
|
|
|
function renderBar(selector, data, options = {}) {
|
|
const element = document.querySelector(selector);
|
|
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
if (!hasSeries(data)) {
|
|
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
|
return;
|
|
}
|
|
|
|
const total = data.series.reduce(function (sum, value) {
|
|
return sum + Number(value || 0);
|
|
}, 0);
|
|
const valueDigits = options.valueDigits ?? 0;
|
|
const suffix = options.suffix || '';
|
|
const isHorizontal = options.horizontal || false;
|
|
const wrappedLabels = data.labels.map(function (label) {
|
|
return wrapAxisLabel(label, options.labelLineLength || 13);
|
|
});
|
|
const maxLabelLines = wrappedLabels.reduce(function (maximum, label) {
|
|
return Math.max(maximum, Array.isArray(label) ? label.length : 1);
|
|
}, 1);
|
|
const labelMaxHeight = Math.max(82, Math.min(maxLabelLines * 18, 160));
|
|
const chartHeight = isHorizontal
|
|
? options.height || 330
|
|
: (options.height || 330) + Math.max(maxLabelLines - 3, 0) * 16;
|
|
|
|
new ApexCharts(element, {
|
|
chart: {
|
|
type: 'bar',
|
|
height: chartHeight,
|
|
parentHeightOffset: 0,
|
|
toolbar: { show: false },
|
|
},
|
|
series: [{
|
|
name: options.name || 'Total',
|
|
data: data.series,
|
|
}],
|
|
colors: options.colors || colorsFor(options.colorOffset || 0),
|
|
plotOptions: {
|
|
bar: {
|
|
borderRadius: 4,
|
|
borderRadiusApplication: 'around',
|
|
horizontal: isHorizontal,
|
|
columnWidth: options.columnWidth || '44%',
|
|
barHeight: options.barHeight || '50%',
|
|
distributed: true,
|
|
dataLabels: {
|
|
position: 'top',
|
|
},
|
|
},
|
|
},
|
|
dataLabels: {
|
|
enabled: true,
|
|
textAnchor: isHorizontal ? 'start' : 'middle',
|
|
offsetX: isHorizontal ? 6 : 0,
|
|
offsetY: isHorizontal ? 0 : -18,
|
|
style: {
|
|
fontSize: '12px',
|
|
fontFamily: fontFamily,
|
|
fontWeight: 500,
|
|
colors: [headingColor],
|
|
},
|
|
formatter: function (value) {
|
|
const percentage = total > 0 ? ((Number(value || 0) / total) * 100).toFixed(1) : '0.0';
|
|
|
|
return `${formatNumber(value, valueDigits)}${suffix ? ' ' + suffix : ''} (${percentage}%)`;
|
|
},
|
|
},
|
|
grid: {
|
|
borderColor: chartColors.border,
|
|
strokeDashArray: 5,
|
|
xaxis: { lines: { show: isHorizontal } },
|
|
yaxis: { lines: { show: !isHorizontal } },
|
|
padding: {
|
|
top: 18,
|
|
right: isHorizontal ? 55 : 16,
|
|
left: 8,
|
|
bottom: isHorizontal ? 0 : Math.max(maxLabelLines - 2, 0) * 8,
|
|
},
|
|
},
|
|
xaxis: {
|
|
categories: isHorizontal ? data.labels : wrappedLabels,
|
|
axisBorder: { show: false },
|
|
axisTicks: { show: false },
|
|
labels: {
|
|
rotate: 0,
|
|
rotateAlways: false,
|
|
trim: false,
|
|
hideOverlappingLabels: false,
|
|
maxHeight: labelMaxHeight,
|
|
style: {
|
|
colors: legendColor,
|
|
fontSize: '12px',
|
|
fontFamily: fontFamily,
|
|
},
|
|
formatter: function (value) {
|
|
if (isHorizontal && valueDigits === 0 && !Number.isInteger(Number(value))) {
|
|
return '';
|
|
}
|
|
|
|
return isHorizontal ? formatNumber(value, valueDigits) : value;
|
|
},
|
|
},
|
|
tooltip: { enabled: false },
|
|
title: {
|
|
text: isHorizontal ? suffix || options.name || '' : '',
|
|
style: {
|
|
color: legendColor,
|
|
fontSize: '12px',
|
|
fontFamily: fontFamily,
|
|
fontWeight: 400,
|
|
},
|
|
},
|
|
},
|
|
yaxis: {
|
|
labels: {
|
|
maxWidth: isHorizontal ? options.labelWidth || 180 : undefined,
|
|
style: {
|
|
colors: legendColor,
|
|
fontSize: '12px',
|
|
fontFamily: fontFamily,
|
|
},
|
|
formatter: function (value) {
|
|
return isHorizontal ? value : formatNumber(value, valueDigits);
|
|
},
|
|
},
|
|
title: {
|
|
text: isHorizontal ? '' : suffix || options.name || '',
|
|
style: {
|
|
color: legendColor,
|
|
fontSize: '12px',
|
|
fontFamily: fontFamily,
|
|
fontWeight: 400,
|
|
},
|
|
},
|
|
},
|
|
legend: { show: false },
|
|
tooltip: {
|
|
enabled: true,
|
|
followCursor: true,
|
|
intersect: false,
|
|
shared: false,
|
|
custom: function ({ series, seriesIndex, dataPointIndex, w }) {
|
|
const value = Number(series[seriesIndex][dataPointIndex] || 0);
|
|
const label = data.labels[dataPointIndex] || w.globals.labels[dataPointIndex];
|
|
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
|
|
const color = w.globals.colors[dataPointIndex] || options.color || chartColors.primary;
|
|
|
|
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);
|
|
"
|
|
>
|
|
${label}:
|
|
<strong>
|
|
${formatNumber(value, valueDigits)}${suffix ? ' ' + suffix : ''}
|
|
(${percentage}%)
|
|
</strong>
|
|
</div>
|
|
`;
|
|
},
|
|
},
|
|
states: {
|
|
hover: {
|
|
filter: {
|
|
type: 'lighten',
|
|
value: 0.08,
|
|
},
|
|
},
|
|
active: {
|
|
filter: {
|
|
type: 'darken',
|
|
value: 0.08,
|
|
},
|
|
},
|
|
},
|
|
}).render();
|
|
}
|
|
|
|
renderDonut(
|
|
'#chart-petani-komoditas',
|
|
chartData.petaniPerKomoditas,
|
|
colorsFor(0),
|
|
{
|
|
totalLabel: 'Petani',
|
|
unit: 'petani',
|
|
valueDigits: 0,
|
|
}
|
|
);
|
|
|
|
renderBar('#chart-luas-kecamatan', chartData.luasLahanPerKecamatan, {
|
|
name: 'Luas lahan',
|
|
suffix: 'ha',
|
|
colors: colorsFor(1),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 2,
|
|
});
|
|
|
|
renderDonut(
|
|
'#chart-kondisi-tanaman',
|
|
chartData.kondisiTanaman,
|
|
colorsFor(2),
|
|
{
|
|
totalLabel: 'Data',
|
|
valueDigits: 0,
|
|
}
|
|
);
|
|
|
|
renderDonut(
|
|
'#chart-status-kepemilikan-lahan',
|
|
chartData.statusKepemilikanLahan,
|
|
colorsFor(3),
|
|
{
|
|
totalLabel: 'Lahan',
|
|
unit: 'lahan',
|
|
valueDigits: 0,
|
|
}
|
|
);
|
|
|
|
renderBar('#chart-serangan-hama', chartData.seranganHama, {
|
|
name: 'Data',
|
|
colors: colorsFor(4),
|
|
height: 330,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
renderBar('#chart-penyakit-tanaman', chartData.penyakitTanaman, {
|
|
name: 'Data',
|
|
colors: colorsFor(5),
|
|
height: 330,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
renderBar('#chart-keparahan-serangan', chartData.keparahanSerangan, {
|
|
name: 'Data',
|
|
colors: colorsFor(6),
|
|
height: 330,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
renderBar('#chart-status-hak-kelola-lahan', chartData.statusHakKelolaLahan, {
|
|
name: 'Petani',
|
|
colors: colorsFor(7),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 180,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
renderBar('#chart-praktik-pemupukan', chartData.praktikPemupukan, {
|
|
name: 'Petani',
|
|
colors: colorsFor(8),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 180,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
let productionChartsRendered = false;
|
|
|
|
function renderProductionCharts() {
|
|
if (productionChartsRendered) {
|
|
return;
|
|
}
|
|
|
|
productionChartsRendered = true;
|
|
|
|
renderBar('#chart-produksi-komoditas', chartData.produksiPerKomoditas, {
|
|
name: 'Produksi',
|
|
suffix: 'kg',
|
|
colors: colorsFor(0),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 0,
|
|
});
|
|
|
|
renderBar('#chart-produktivitas-komoditas', chartData.produktivitasPerKomoditas, {
|
|
name: 'Produktivitas',
|
|
suffix: 'kg/ha',
|
|
colors: colorsFor(2),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 2,
|
|
});
|
|
|
|
renderBar('#chart-luas-panen-komoditas', chartData.luasPanenPerKomoditas, {
|
|
name: 'Luas panen',
|
|
suffix: 'ha',
|
|
colors: colorsFor(4),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 2,
|
|
});
|
|
|
|
renderDonut(
|
|
'#chart-status-panen',
|
|
chartData.statusPanen,
|
|
colorsFor(6),
|
|
{
|
|
totalLabel: 'Catatan',
|
|
valueDigits: 0,
|
|
}
|
|
);
|
|
|
|
renderBar('#chart-survey-produktivitas-ha', chartData.surveyProduktivitasHa, {
|
|
name: 'Produktivitas',
|
|
suffix: 'ha/tahun',
|
|
colors: colorsFor(1),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 2,
|
|
});
|
|
|
|
renderBar('#chart-survey-produktivitas-pohon', chartData.surveyProduktivitasPohon, {
|
|
name: 'Produktivitas',
|
|
suffix: 'pohon/tahun',
|
|
colors: colorsFor(3),
|
|
height: 330,
|
|
horizontal: true,
|
|
labelWidth: 150,
|
|
valueDigits: 2,
|
|
});
|
|
}
|
|
|
|
document
|
|
.querySelector('[data-bs-target="#indicator-tab-produksi"]')
|
|
?.addEventListener('shown.bs.tab', renderProductionCharts);
|
|
|
|
if (document.querySelector('#indicator-tab-produksi')?.classList.contains('active')) {
|
|
renderProductionCharts();
|
|
}
|
|
});
|
|
</script>
|
|
@endpush
|