Update Produktivitas Dashboard
This commit is contained in:
@@ -0,0 +1,647 @@
|
|||||||
|
@extends('layout.main')
|
||||||
|
|
||||||
|
@push('style')
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<style>
|
||||||
|
body { background-color: #f8faf9; font-family: 'Public Sans', sans-serif; }
|
||||||
|
|
||||||
|
.card { border: none; border-radius: 15px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
|
||||||
|
|
||||||
|
/* Map Container */
|
||||||
|
#map { height: 600px; width: 100%; border-radius: 15px; }
|
||||||
|
|
||||||
|
/* Sidebar Mini Stats */
|
||||||
|
.stat-card-mini { transition: all 0.3s ease; border-left: 5px solid transparent; }
|
||||||
|
.stat-card-mini:hover { transform: translateX(5px); border-left-color: var(--agri-green); }
|
||||||
|
|
||||||
|
/* Map Switcher Manual */
|
||||||
|
.map-controls-custom {
|
||||||
|
position: absolute; top: 20px; right: 20px; z-index: 1000;
|
||||||
|
background: white; padding: 5px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styling */
|
||||||
|
.table thead th { background-color: #f8f9fa; text-transform: uppercase; font-size: 11px; letter-spacing: 1px; }
|
||||||
|
.avatar-label { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 10px; font-weight: bold; }
|
||||||
|
|
||||||
|
.leaflet-tooltip.leaflet-modern-tooltip {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #333;
|
||||||
|
font-family: 'Public Sans', sans-serif; /* Pastikan font dashboard Anda */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menghilangkan panah kecil di bawah tooltip */
|
||||||
|
.leaflet-modern-tooltip:before {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Gaya untuk Modern Map Card kita */
|
||||||
|
.modern-map-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
padding: 16px;
|
||||||
|
width: 190px; /* Lebar card */
|
||||||
|
border: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bagian Header */
|
||||||
|
.card-header-region {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.region-label {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #888;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.region-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 0;
|
||||||
|
color: #1e1e1e;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bagian Stats Row (Flexbox untuk menyusun ikon & teks) */
|
||||||
|
.d-flex { display: flex; }
|
||||||
|
.align-items-center { align-items: center; }
|
||||||
|
.mb-2 { margin-bottom: 8px; }
|
||||||
|
.me-2 { margin-right: 10px; }
|
||||||
|
.fw-bold { font-weight: 700; }
|
||||||
|
.small { font-size: 12px; }
|
||||||
|
|
||||||
|
/* Gaya Ikon Baris Statistik */
|
||||||
|
.stat-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warna Latar Belakang Ringan untuk Ikon (Bootstrap style) */
|
||||||
|
.bg-success-light { background-color: rgba(40, 167, 69, 0.1); }
|
||||||
|
.bg-primary-light { background-color: rgba(0, 123, 255, 0.1); }
|
||||||
|
.text-success { color: #28a745; }
|
||||||
|
.text-primary { color: #007bff; }
|
||||||
|
|
||||||
|
.stat-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #222;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.text-muted { color: #6c757d; }
|
||||||
|
|
||||||
|
/* Bagian Footer Action */
|
||||||
|
.action-prompt {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
/* Sedikit animasi pada chevron saat di-hover */
|
||||||
|
.action-prompt i {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.leaflet-modern-tooltip:hover .action-prompt i {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gaya Bootstrap Helper Ringan (untuk jaga-jaga jika dashboard tidak memuatnya) */
|
||||||
|
.border-top { border-top: 1px solid rgba(0,0,0,0.08) !important; }
|
||||||
|
.border-bottom { border-bottom: 1px solid rgba(0,0,0,0.08) !important; }
|
||||||
|
.py-2 { padding-top: 8px !important; padding-bottom: 8px !important; }
|
||||||
|
.my-2 { margin-top: 8px !important; margin-bottom: 8px !important; }
|
||||||
|
|
||||||
|
.leaflet-popup-wrapper.leaflet-modern-popup-wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-modern-popup-wrapper .leaflet-popup-content-wrapper {
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0 !important; /* Hapus padding default Leaflet agar full width */
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
|
||||||
|
border: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-modern-popup-wrapper .leaflet-popup-content {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Penyesuaian panah kecil di bawah */
|
||||||
|
.leaflet-modern-popup-wrapper .leaflet-popup-tip {
|
||||||
|
background: #f8f9fa; /* Sesuaikan dengan warna body footer popup (bg-light) */
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Gaya untuk Modern Lahan Popup kita */
|
||||||
|
.modern-lahan-popup {
|
||||||
|
font-family: 'Public Sans', sans-serif;
|
||||||
|
min-width: 270px; /* Lebar card */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography Hierarki */
|
||||||
|
.modern-lahan-popup .h5 { font-size: 16px; margin: 0; }
|
||||||
|
.modern-lahan-popup .fs-5 { font-size: 1.25rem; }
|
||||||
|
.modern-lahan-popup small { font-size: 11px; }
|
||||||
|
|
||||||
|
/* Header Farmer */
|
||||||
|
.rounded-top-12 { border-radius: 12px 12px 0 0; }
|
||||||
|
.header-farmer {
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
.label-header {
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.farmer-name {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body Details */
|
||||||
|
.details-body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.rounded-bottom-12 { border-radius: 0 0 12px 12px; }
|
||||||
|
|
||||||
|
.label-detail {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
.value-detail {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 1px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
.value-commodity {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ikon Container (derived from stats card style) */
|
||||||
|
.icon-stat-container {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper Colors (for icons & backgrounds) */
|
||||||
|
.text-danger-light { color: #dc3545 !important; }
|
||||||
|
.text-danger { color: #dc3545 !important; }
|
||||||
|
.text-success { color: #28a745 !important; }
|
||||||
|
|
||||||
|
/* Background Ringan (Bootstrap Style) */
|
||||||
|
.text-success-light { background-color: rgba(40, 167, 69, 0.1) !important; }
|
||||||
|
.text-danger-light { background-color: rgba(220, 53, 69, 0.1) !important; }
|
||||||
|
.bg-primary-light { background-color: rgba(0, 123, 255, 0.1) !important; }
|
||||||
|
|
||||||
|
/* Bootstrap-like Utilities (untuk berjaga-jaga jika dash belum memuatnya) */
|
||||||
|
.me-2 { margin-right: 10px !important; }
|
||||||
|
.mb-3 { margin-bottom: 16px !important; }
|
||||||
|
.ps-2 { padding-left: 12px !important; }
|
||||||
|
.col-6 { width: 50%; }
|
||||||
|
.col-12 { width: 100%; }
|
||||||
|
.border { border: 1px solid rgba(0,0,0,0.08) !important; }
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="card mb-3 stat-card-mini">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="avatar bg-label-success rounded p-2"><i class="ti tabler-mountain fs-3"></i></div>
|
||||||
|
<!-- <span class="badge bg-label-success">+2.4%</span> -->
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<small class="text-muted text-uppercase">Total Land Area</small>
|
||||||
|
<h4 class="mb-0 fw-bold">{{ number_format($luasTotal, 1) }} <small class="text-muted">ha</small></h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3 stat-card-mini">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="avatar bg-label-success rounded p-2"><i class="ti tabler-map fs-3"></i></div>
|
||||||
|
<!-- <span class="badge bg-label-success"></span> -->
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<small class="text-muted text-uppercase">Total Lahan</small>
|
||||||
|
<h4 class="mb-0 fw-bold">{{ $totallahan }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3 stat-card-mini">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="avatar bg-label-primary rounded p-2"><i class="ti tabler-users fs-3"></i></div>
|
||||||
|
<span class="badge bg-success text-white text-center">Active</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<small class="text-muted text-uppercase">Registered Farmers</small>
|
||||||
|
<h4 class="mb-0 fw-bold">{{ $totalpetani }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header"><h6 class="mb-0 fw-bold">Distribusi Komoditas</h6></div>
|
||||||
|
<div class="card-body">
|
||||||
|
@foreach(['Vanilla' => 45, 'Candlenut' => 30, 'Cocoa' => 15, 'Coffee' => 10] as $name => $val)
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<small class="fw-bold">{{ $name }}</small>
|
||||||
|
<small class="text-muted">{{ $val }}%</small>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 6px;">
|
||||||
|
<div class="progress-bar bg-success" style="width: {{ $val }}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<div class="card mb-4 overflow-hidden position-relative">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center bg-white py-3">
|
||||||
|
<h6 class="mb-0 fw-bold"><i class="ti tabler-map-2 me-2"></i>Peta Sebaran Lahan</h6>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<button class="btn btn-outline-secondary active" onclick="setMapLayer('satellite')">Satellite</button>
|
||||||
|
<button class="btn btn-outline-secondary" onclick="setMapLayer('topo')">Topographic</button>
|
||||||
|
<button class="btn btn-outline-secondary" onclick="setMapLayer('parcels')">Parcels</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="map">
|
||||||
|
<button id="btnBackMap" class="btn btn-sm btn-dark shadow" style="display:none; position:absolute; top:20px; left:50px; z-index:1000;">
|
||||||
|
<i class="ti tabler-arrow-left me-1"></i> Kembali ke Atas
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0 fw-bold">Produktivitas Petani Regional</h6>
|
||||||
|
<button class="btn btn-sm btn-success"><i class="ti tabler-plus me-1"></i> Bulk Action</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nama Petani</th>
|
||||||
|
<th>ID Lahan</th>
|
||||||
|
<th>Komoditas</th>
|
||||||
|
<th>Luas (HA)</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($petaniList as $p)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="avatar-label bg-label-secondary me-2">{{ substr($p->nama, 0, 1) }}</div>
|
||||||
|
<span class="fw-bold">{{ $p->nama }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><span class="text-muted small">TR-{{ $p->id }}</span></td>
|
||||||
|
<td><span class="badge bg-label-info">Vanilla</span></td>
|
||||||
|
<td class="fw-bold">{{ number_format($p->lahan->sum('luas_lahan'), 1) }}</td>
|
||||||
|
<td><span class="badge bg-success">Verified</span></td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
// 1. Setup Base Map (Gunakan HTTPS untuk menghindari error mixed-content)
|
||||||
|
var satellite = L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 22 });
|
||||||
|
var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png');
|
||||||
|
var parcels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png');
|
||||||
|
|
||||||
|
var map = L.map('map', { center: [-0.789, 113.921], zoom: 5, layers: [satellite], zoomControl: false });
|
||||||
|
L.control.zoom({ position: 'topleft' }).addTo(map);
|
||||||
|
|
||||||
|
// Map Switcher Custom
|
||||||
|
function setMapLayer(type) {
|
||||||
|
map.removeLayer(satellite); map.removeLayer(topo); map.removeLayer(parcels);
|
||||||
|
if(type === 'satellite') satellite.addTo(map);
|
||||||
|
if(type === 'topo') topo.addTo(map);
|
||||||
|
if(type === 'parcels') parcels.addTo(map);
|
||||||
|
event.target.parentElement.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
|
||||||
|
event.target.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Data dari Server
|
||||||
|
var dataLevels = {
|
||||||
|
'provinsi': @json($geoProvinsi),
|
||||||
|
'kabupaten': @json($geoKabupaten),
|
||||||
|
'kecamatan': @json($geoKecamatan),
|
||||||
|
'desa': @json($geoDesa),
|
||||||
|
'lahan': @json($geoLahan)
|
||||||
|
};
|
||||||
|
|
||||||
|
var currentLayerGroup = L.featureGroup().addTo(map);
|
||||||
|
var mapHistory = [];
|
||||||
|
var btnBack = document.getElementById('btnBackMap');
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// FUNGSI PINTAR: DETEKSI LATITUDE/LONGITUDE
|
||||||
|
// ==========================================
|
||||||
|
function ensureLatLon(coords) {
|
||||||
|
if (typeof coords[0] === 'number') {
|
||||||
|
// Indonesia: Longitude = ~95 s/d ~141 | Latitude = ~-11 s/d ~6
|
||||||
|
// Jika angka pertama melebihi 90, itu PASTI Longitude. Kita balik agar menjadi [Lat, Lon]
|
||||||
|
if (Math.abs(coords[0]) > 90) {
|
||||||
|
return [coords[1], coords[0]];
|
||||||
|
}
|
||||||
|
// Jika angka pertama kecil, berarti sudah benar [Lat, Lon], biarkan saja
|
||||||
|
return [coords[0], coords[1]];
|
||||||
|
}
|
||||||
|
return coords.map(ensureLatLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fungsi Render Peta Dinamis
|
||||||
|
function renderLevel(level, parentId = null, fallbackBounds = null) {
|
||||||
|
var dataToRender = dataLevels[level];
|
||||||
|
|
||||||
|
if (parentId !== null) {
|
||||||
|
dataToRender = dataToRender.filter(item => item.parent_id == parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataToRender.length === 0) {
|
||||||
|
alert(`Belum ada data pemetaan ${level} yang terdaftar di wilayah ini.`);
|
||||||
|
if (mapHistory.length > 0) {
|
||||||
|
mapHistory.pop();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLayerGroup.clearLayers();
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// FITUR BARU: GAMBAR BATAS DESA SAAT MELIHAT LAHAN
|
||||||
|
// =========================================================
|
||||||
|
if (level === 'lahan' && parentId !== null) {
|
||||||
|
// Cari data desa yang sedang diklik
|
||||||
|
var parentDesa = dataLevels['desa'].find(d => d.id == parentId);
|
||||||
|
|
||||||
|
if (parentDesa && parentDesa.polygon) {
|
||||||
|
try {
|
||||||
|
var desaGeojson = typeof parentDesa.polygon === 'string' ? JSON.parse(parentDesa.polygon) : parentDesa.polygon;
|
||||||
|
var desaLayer;
|
||||||
|
|
||||||
|
// Style untuk batas Desa (Garis putus-putus merah, isian transparan)
|
||||||
|
var desaStyle = {
|
||||||
|
color: '#dc3545', // Merah
|
||||||
|
weight: 2,
|
||||||
|
dashArray: '5, 8', // Garis putus-putus
|
||||||
|
fillColor: 'none', // Tidak ada warna isian
|
||||||
|
interactive: false // Tidak bisa diklik agar tidak menutupi klik lahan
|
||||||
|
};
|
||||||
|
|
||||||
|
if (desaGeojson !== null && typeof desaGeojson === 'object' && desaGeojson.type) {
|
||||||
|
desaLayer = L.geoJSON(desaGeojson, {
|
||||||
|
style: desaStyle,
|
||||||
|
coordsToLatLng: function (coords) {
|
||||||
|
var a = coords[0], b = coords[1];
|
||||||
|
if (Math.abs(a) > 90) return L.latLng(b, a);
|
||||||
|
if (Math.abs(b) > 90) return L.latLng(a, b);
|
||||||
|
return L.latLng(b, a);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (Array.isArray(desaGeojson)) {
|
||||||
|
var safeCoordsDesa = ensureLatLon(desaGeojson);
|
||||||
|
desaLayer = L.polygon(safeCoordsDesa, desaStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan layer desa ke peta
|
||||||
|
if (desaLayer) {
|
||||||
|
currentLayerGroup.addLayer(desaLayer);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Gagal render batas desa:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
// Render data utama (Lahan / Wilayah Administratif)
|
||||||
|
dataToRender.forEach(function(item) {
|
||||||
|
if (!item.polygon) return;
|
||||||
|
try {
|
||||||
|
var geojsonData = typeof item.polygon === 'string' ? JSON.parse(item.polygon) : item.polygon;
|
||||||
|
var layer;
|
||||||
|
|
||||||
|
var styleOpts = level === 'lahan'
|
||||||
|
? { color: '#28a745', weight: 2, fillColor: '#28a745', fillOpacity: 0.6 }
|
||||||
|
: { color: '#f39c12', weight: 2, fillColor: '#f1c40f', fillOpacity: 0.3 };
|
||||||
|
|
||||||
|
if (geojsonData !== null && typeof geojsonData === 'object' && geojsonData.type) {
|
||||||
|
layer = L.geoJSON(geojsonData, {
|
||||||
|
style: styleOpts,
|
||||||
|
coordsToLatLng: function (coords) {
|
||||||
|
var a = coords[0], b = coords[1];
|
||||||
|
if (Math.abs(a) > 90) return L.latLng(b, a);
|
||||||
|
if (Math.abs(b) > 90) return L.latLng(a, b);
|
||||||
|
return L.latLng(b, a);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (Array.isArray(geojsonData)) {
|
||||||
|
var safeCoords = ensureLatLon(geojsonData);
|
||||||
|
layer = L.polygon(safeCoords, styleOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer) {
|
||||||
|
if (level === 'lahan') {
|
||||||
|
// 1. Handling Khusus untuk Komoditas/Tanaman
|
||||||
|
var listTanaman = item.tanaman && item.tanaman.length > 0 ? item.tanaman.join(', ') : null;
|
||||||
|
|
||||||
|
// Teks & Style conditional untuk komoditas kosong
|
||||||
|
var komoditasText = listTanaman
|
||||||
|
? listTanaman
|
||||||
|
: '<span class="text-danger-light fw-medium small">Belum terdaftar</span>';
|
||||||
|
|
||||||
|
// Ikon conditional (ikon tanaman jika ada, ikon off jika kosong)
|
||||||
|
var cropIcon = listTanaman ? 'ti tabler-plant' : 'ti tabler-leaf-off';
|
||||||
|
var cropIconColor = listTanaman ? 'text-success' : 'text-danger';
|
||||||
|
|
||||||
|
// 2. Tampilan Baru Popup Lahan (Modern Card)
|
||||||
|
var popupContent = `
|
||||||
|
<div class="modern-lahan-popup">
|
||||||
|
<div class="p-3 bg-white rounded-top-12 header-farmer">
|
||||||
|
<div class="d-flex align-items-center mb-1">
|
||||||
|
<small class="label-header text-uppercase text-muted fw-semibold">Pemilik Lahan</small>
|
||||||
|
</div>
|
||||||
|
<h6 class="mb-0 fw-bold farmer-name h5">${item.nama_petani}</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 bg-light rounded-bottom-12 details-body">
|
||||||
|
<div class="row g-2 mb-3">
|
||||||
|
<div class="col-12 d-flex align-items-center">
|
||||||
|
<div class="icon-stat-container rounded-circle bg-white text-muted border me-2">
|
||||||
|
<i class="ti tabler-hash fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small class="d-block text-muted text-uppercase fw-medium label-detail">ID Lahan</small>
|
||||||
|
<span class="fw-bold text-dark value-detail">TR-${item.id}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-1">
|
||||||
|
<div class="col-6 d-flex align-items-center">
|
||||||
|
<div class="icon-stat-container rounded ${cropIconColor}-light me-2">
|
||||||
|
<i class="${cropIcon} fs-5 ${cropIconColor}"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<small class="d-block text-muted text-uppercase fw-medium label-detail">Komoditas</small>
|
||||||
|
<span class="fw-semibold text-dark value-commodity">${komoditasText}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 d-flex align-items-center border-start ps-2">
|
||||||
|
<div class="icon-stat-container rounded bg-primary-light me-2">
|
||||||
|
<i class="ti tabler-dimensions fs-5 text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small class="d-block text-muted text-uppercase fw-medium label-detail">Luas Lahan</small>
|
||||||
|
<span class="fw-bold text-dark value-detail">${parseFloat(item.luas).toFixed(1)} Ha</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Pengaturan Popup Leaflet
|
||||||
|
layer.bindPopup(popupContent, {
|
||||||
|
maxWidth: 300, // Membatasi lebar agar tetap rapi
|
||||||
|
closeButton: true, // Menampilkan tombol close pro
|
||||||
|
offset: [0, 0], // Penyesuaian jarak tip
|
||||||
|
className: 'leaflet-modern-popup-wrapper' // Kelas CSS kustom (Langkah 2)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// FORMAT TOOLTIP BARU: Struktur Card Modern
|
||||||
|
// Menggunakan template literal (backticks ``) agar rapi
|
||||||
|
var tooltipContent = `
|
||||||
|
<div class="modern-map-card">
|
||||||
|
<div class="card-header-region">
|
||||||
|
<span class="region-label">Wilayah</span>
|
||||||
|
<h6 class="region-name">${item.nama}</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body-stats border-top border-bottom py-2 my-2">
|
||||||
|
<div class="stat-row d-flex align-items-center mb-2">
|
||||||
|
<div class="stat-icon bg-success-light text-success me-2 rounded-p-2">
|
||||||
|
<i class="ti tabler-map"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<small class="text-muted text-uppercase fw-medium" style="font-size: 10px;">Jumlah Lahan</small>
|
||||||
|
<div class="stat-value fw-bold">${parseInt(item.total_lahan)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-row d-flex align-items-center">
|
||||||
|
<div class="stat-icon bg-primary-light text-primary me-2 rounded-p-2">
|
||||||
|
<i class="ti tabler-area"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-info">
|
||||||
|
<small class="text-muted text-uppercase fw-medium" style="font-size: 10px;">Total Luas</small>
|
||||||
|
<div class="stat-value fw-bold">${parseFloat(item.total_luas).toFixed(2)} <small class="text-muted">Ha</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer-action text-center mt-2">
|
||||||
|
<span class="action-prompt text-primary fw-medium small">
|
||||||
|
Klik untuk detail <i class="ti tabler-chevron-right ms-1"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Pengaturan Tooltip Leaflet
|
||||||
|
layer.bindTooltip(tooltipContent, {
|
||||||
|
sticky: true, // Ikut kursor
|
||||||
|
direction: 'top', // Muncul di atas kursor
|
||||||
|
className: 'leaflet-modern-tooltip', // Kelas CSS kustom (Langkah 2)
|
||||||
|
offset: [0, -10] // Jarak dari kursor agar tidak menutupi poligon
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logika Klik (Hapalan dari sebelumnya - jangan diubah)
|
||||||
|
layer.on('click', function(e) {
|
||||||
|
L.DomEvent.stopPropagation(e);
|
||||||
|
var nextLevel = level === 'provinsi' ? 'kabupaten'
|
||||||
|
: level === 'kabupaten' ? 'kecamatan'
|
||||||
|
: level === 'kecamatan' ? 'desa' : 'lahan';
|
||||||
|
|
||||||
|
mapHistory.push({ level: level, parentId: parentId, bounds: currentLayerGroup.getBounds() });
|
||||||
|
renderLevel(nextLevel, item.id, e.target.getBounds());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentLayerGroup.addLayer(layer);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Gagal memproses data level", level, "ID", item.id, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (btnBack) btnBack.style.display = mapHistory.length > 0 ? 'block' : 'none';
|
||||||
|
|
||||||
|
if (currentLayerGroup.getLayers().length > 0) {
|
||||||
|
var bounds = currentLayerGroup.getBounds();
|
||||||
|
if (bounds.isValid()) {
|
||||||
|
map.fitBounds(bounds, { padding: [30, 30], maxZoom: 18 });
|
||||||
|
}
|
||||||
|
} else if (fallbackBounds && fallbackBounds.isValid()) {
|
||||||
|
map.fitBounds(fallbackBounds, { padding: [30, 30], maxZoom: 18 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aksi Tombol Kembali
|
||||||
|
if (btnBack) {
|
||||||
|
btnBack.addEventListener('click', function() {
|
||||||
|
var prevState = mapHistory.pop();
|
||||||
|
if (prevState) {
|
||||||
|
renderLevel(prevState.level, prevState.parentId);
|
||||||
|
if (prevState.bounds && prevState.bounds.isValid()) {
|
||||||
|
map.fitBounds(prevState.bounds, { padding: [30, 30] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pastikan ukuran peta sesuai dengan container saat dimuat
|
||||||
|
setTimeout(() => {
|
||||||
|
map.invalidateSize();
|
||||||
|
// Render Awal: Provinsi
|
||||||
|
renderLevel('provinsi', null);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
Reference in New Issue
Block a user