407 lines
20 KiB
JavaScript
407 lines
20 KiB
JavaScript
const fs = require('fs');
|
|
const admin = fs.readFileSync('admin.html', 'utf8');
|
|
|
|
// Extract CSS
|
|
const cssMatch = admin.match(/<style>([\s\S]*?)<\/style>/);
|
|
const css = cssMatch ? cssMatch[1] : '';
|
|
|
|
// Extract Sidebar
|
|
const sidebarMatch = admin.match(/<aside class="sidebar">([\s\S]*?)<\/aside>/);
|
|
let sidebarBase = sidebarMatch ? sidebarMatch[0] : '';
|
|
|
|
// Function to build SPBU
|
|
function buildSPBU() {
|
|
let sidebar = sidebarBase;
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Data<\/div>[\s\S]*?<div class="sidebar-section">/, '<div class="sidebar-section">');
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Tools<\/div>[\s\S]*?<div class="sidebar-section">/, '<div class="sidebar-section">');
|
|
sidebar = sidebar.replace(/<div class="sidebar-item" data-panel="panel-dashboard">[\s\S]*?<\/div>/g, '');
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Settings<\/div>[\s\S]*?<\/nav>/, '<div class="sidebar-section-title">Account</div><div class="sidebar-menu"><a href="login.html" class="sidebar-item" style="text-decoration:none;"><div class="sidebar-item-icon">⎋</div><div class="sidebar-item-text">Login Admin</div></a></div></nav>');
|
|
|
|
const html = `<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Peta SPBU Publik</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<style>
|
|
${css}
|
|
.controls-panel { position: absolute; top: 80px; right: 20px; background: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); z-index: 1000; width: 280px; color:#333; }
|
|
.controls-panel h3 { font-size: 14px; margin-bottom: 12px; color: #1e293b; }
|
|
.btn-action { display: block; width: 100%; padding: 10px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; transition: 0.2s; margin-bottom: 10px; }
|
|
.btn-action:hover { background: #2563eb; }
|
|
.btn-action:disabled { background: #94a3b8; cursor: not-allowed; }
|
|
|
|
.info-box { background: #f1f5f9; padding: 12px; border-radius: 6px; font-size: 13px; color: #334155; margin-top: 10px; display: none; }
|
|
.info-box.active { display: block; }
|
|
|
|
.search-box2 { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 13px; outline: none; }
|
|
.search-box2:focus { border-color: #3b82f6; }
|
|
|
|
.legend-panel { position: absolute; bottom: 30px; right: 20px; background: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); z-index: 1000; width: 200px; font-size: 13px; color: #334155; }
|
|
.legend-panel h3 { font-size: 14px; margin-bottom: 12px; color: #1e293b; }
|
|
.legend-item2 { display: flex; align-items: center; margin-bottom: 8px; }
|
|
.legend-icon2 { width: 16px; height: 16px; border-radius: 50%; margin-right: 10px; display: inline-block; }
|
|
.bg-blue { background: #3b82f6; }
|
|
.bg-red { background: #ef4444; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-wrapper">
|
|
${sidebar}
|
|
|
|
<div class="main-wrapper">
|
|
<header>
|
|
<div class="header-title">
|
|
<h1 id="page-title">Peta Persebaran SPBU</h1>
|
|
<div class="header-breadcrumb">
|
|
<span id="breadcrumb-text">Public Map</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content-container">
|
|
<div class="content-panel active" style="position: relative;">
|
|
<div id="map" style="width:100%; height:100%; z-index:1;"></div>
|
|
|
|
<div class="controls-panel">
|
|
<h3>Fitur SPBU Terdekat</h3>
|
|
<input type="text" id="search-spbu" class="search-box2" placeholder="Cari nama SPBU...">
|
|
<button id="btn-location" class="btn-action">Dapatkan Lokasi Saya</button>
|
|
<button id="btn-nearest" class="btn-action" disabled>Cari SPBU Terdekat</button>
|
|
|
|
<div id="info-box" class="info-box">
|
|
<strong>SPBU Terdekat:</strong> <span id="nearest-name">-</span><br>
|
|
<strong>Jarak:</strong> <span id="nearest-distance">-</span> km
|
|
</div>
|
|
</div>
|
|
|
|
<div class="legend-panel">
|
|
<h3>Legenda</h3>
|
|
<div class="legend-item2">
|
|
<span class="legend-icon2 bg-blue"></span> SPBU
|
|
</div>
|
|
<div class="legend-item2">
|
|
<span class="legend-icon2 bg-red"></span> Lokasi Anda
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
|
|
<script>
|
|
// Sidebar toggle
|
|
document.getElementById('sidebar-toggle').addEventListener('click', () => {
|
|
document.querySelector('.sidebar').classList.toggle('collapsed');
|
|
});
|
|
|
|
// Init Map
|
|
const map = L.map('map').setView([-0.026, 109.342], 13);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
let spbuData = [];
|
|
let spbuLayer = L.layerGroup().addTo(map);
|
|
let userMarker = null;
|
|
let routeLine = null;
|
|
let userLocation = null;
|
|
|
|
const spbuIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
|
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
|
});
|
|
|
|
const userIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
|
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
|
});
|
|
|
|
async function loadSPBU() {
|
|
try {
|
|
const res = await fetch('backend/read.php');
|
|
const result = await res.json();
|
|
if (result.status === 'success' && result.data) {
|
|
spbuData = result.data;
|
|
renderSPBU();
|
|
}
|
|
} catch (err) { console.error("Gagal memuat SPBU:", err); }
|
|
}
|
|
|
|
function renderSPBU(filterText = '') {
|
|
spbuLayer.clearLayers();
|
|
spbuData.forEach(spbu => {
|
|
if (filterText && !spbu.nama.toLowerCase().includes(filterText.toLowerCase())) return;
|
|
const lat = parseFloat(spbu.latitude);
|
|
const lng = parseFloat(spbu.longitude);
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
L.marker([lat, lng], {icon: spbuIcon})
|
|
.bindPopup('<strong>' + spbu.nama + '</strong><br>No: ' + spbu.no_spbu + '<br>Status: ' + spbu.status)
|
|
.addTo(spbuLayer);
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById('search-spbu').addEventListener('input', (e) => {
|
|
renderSPBU(e.target.value);
|
|
});
|
|
|
|
document.getElementById('btn-location').addEventListener('click', () => {
|
|
if (!navigator.geolocation) return alert("Geolocation tidak didukung");
|
|
const btn = document.getElementById('btn-location');
|
|
btn.textContent = "Mencari...";
|
|
navigator.geolocation.getCurrentPosition((pos) => {
|
|
const lat = pos.coords.latitude;
|
|
const lng = pos.coords.longitude;
|
|
userLocation = [lng, lat];
|
|
if (userMarker) map.removeLayer(userMarker);
|
|
userMarker = L.marker([lat, lng], {icon: userIcon}).bindPopup("Lokasi Anda").addTo(map);
|
|
map.flyTo([lat, lng], 14);
|
|
btn.textContent = "Lokasi Diperbarui";
|
|
document.getElementById('btn-nearest').disabled = false;
|
|
}, (err) => {
|
|
alert("Gagal: " + err.message);
|
|
btn.textContent = "Dapatkan Lokasi Saya";
|
|
});
|
|
});
|
|
|
|
document.getElementById('btn-nearest').addEventListener('click', () => {
|
|
if (!userLocation || spbuData.length === 0) return;
|
|
let nearestSPBU = null, shortestDistance = Infinity;
|
|
const userPoint = turf.point(userLocation);
|
|
spbuData.forEach(spbu => {
|
|
const lat = parseFloat(spbu.latitude);
|
|
const lng = parseFloat(spbu.longitude);
|
|
if (isNaN(lat) || isNaN(lng)) return;
|
|
const dist = turf.distance(userPoint, turf.point([lng, lat]), {units: 'kilometers'});
|
|
if (dist < shortestDistance) { shortestDistance = dist; nearestSPBU = spbu; }
|
|
});
|
|
if (nearestSPBU) {
|
|
if (routeLine) map.removeLayer(routeLine);
|
|
const lat = parseFloat(nearestSPBU.latitude);
|
|
const lng = parseFloat(nearestSPBU.longitude);
|
|
routeLine = L.polyline([[userLocation[1], userLocation[0]], [lat, lng]], { color: '#ef4444', weight: 4, dashArray: '10, 10' }).addTo(map);
|
|
map.fitBounds(routeLine.getBounds(), { padding: [50, 50] });
|
|
document.getElementById('nearest-name').textContent = nearestSPBU.nama;
|
|
document.getElementById('nearest-distance').textContent = shortestDistance.toFixed(2);
|
|
document.getElementById('info-box').classList.add('active');
|
|
}
|
|
});
|
|
|
|
loadSPBU();
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
fs.writeFileSync('spbu.html', html);
|
|
console.log("spbu.html updated");
|
|
}
|
|
|
|
// Function to build Poverty
|
|
function buildPoverty() {
|
|
let sidebar = sidebarBase;
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Data<\/div>[\s\S]*?<div class="sidebar-section">/, '<div class="sidebar-section">');
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Tools<\/div>[\s\S]*?<div class="sidebar-section">/, '<div class="sidebar-section">');
|
|
sidebar = sidebar.replace(/<div class="sidebar-item" data-panel="panel-dashboard">[\s\S]*?<\/div>/g, '');
|
|
sidebar = sidebar.replace(/<div class="sidebar-section-title">Settings<\/div>[\s\S]*?<\/nav>/, '<div class="sidebar-section-title">Account</div><div class="sidebar-menu"><a href="admin.html" class="sidebar-item" style="text-decoration:none;"><div class="sidebar-item-icon">⚙</div><div class="sidebar-item-text">Dashboard Admin</div></a><div class="sidebar-item" id="logout-btn"><div class="sidebar-item-icon">⎋</div><div class="sidebar-item-text">Logout</div></div></div></nav>');
|
|
|
|
const html = `<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Peta Kemiskinan & Rumah Ibadah</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<style>
|
|
${css}
|
|
.controls-panel { position: absolute; top: 80px; right: 20px; background: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); z-index: 1000; width: 280px; color:#333; }
|
|
.controls-panel h3 { font-size: 14px; margin-bottom: 12px; color: #1e293b; }
|
|
.search-box2 { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 13px; outline: none; }
|
|
.search-box2:focus { border-color: #3b82f6; }
|
|
|
|
.popup-slider { width: 100%; margin-top: 10px; }
|
|
.popup-radius-label { font-size: 12px; font-weight: 600; color: #334155; display: block; margin-top: 10px; }
|
|
|
|
.legend-panel { position: absolute; bottom: 30px; right: 20px; background: white; padding: 16px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); z-index: 1000; width: 250px; font-size: 13px; color: #334155; }
|
|
.legend-panel h3 { font-size: 14px; margin-bottom: 12px; color: #1e293b; }
|
|
.legend-item2 { display: flex; align-items: center; margin-bottom: 8px; }
|
|
.legend-icon2 { width: 16px; height: 16px; border-radius: 50%; margin-right: 10px; display: inline-block; }
|
|
.bg-black { background: #1e293b; }
|
|
.bg-red { background: #ef4444; }
|
|
.bg-green { background: #22c55e; }
|
|
|
|
#loader { position: fixed; top:0; left:0; width:100%; height:100%; background: #0f172a; color: white; display: flex; justify-content: center; align-items: center; z-index: 9999; font-size: 18px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="loader">Verifikasi Akses...</div>
|
|
|
|
<div class="app-wrapper">
|
|
${sidebar}
|
|
|
|
<div class="main-wrapper">
|
|
<header>
|
|
<div class="header-title">
|
|
<h1 id="page-title">Peta Analisis Kemiskinan</h1>
|
|
<div class="header-breadcrumb">
|
|
<span id="breadcrumb-text" style="color:#fff; font-weight:normal;"></span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content-container">
|
|
<div class="content-panel active" style="position: relative;">
|
|
<div id="map" style="width:100%; height:100%; z-index:1;"></div>
|
|
|
|
<div class="controls-panel">
|
|
<h3>Pencarian Data</h3>
|
|
<input type="text" id="search-ibadah" class="search-box2" placeholder="Cari Rumah Ibadah...">
|
|
<input type="text" id="search-miskin" class="search-box2" placeholder="Cari Penduduk Miskin (Nama KK)...">
|
|
</div>
|
|
|
|
<div class="legend-panel">
|
|
<h3>Legenda</h3>
|
|
<div class="legend-item2">
|
|
<span class="legend-icon2 bg-green"></span> Rumah Ibadah
|
|
</div>
|
|
<div class="legend-item2">
|
|
<span class="legend-icon2 bg-red"></span> Penduduk Miskin (Dalam Radius)
|
|
</div>
|
|
<div class="legend-item2">
|
|
<span class="legend-icon2 bg-black"></span> Penduduk Miskin (Luar Radius)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
|
|
<script>
|
|
// Sidebar toggle
|
|
document.getElementById('sidebar-toggle').addEventListener('click', () => {
|
|
document.querySelector('.sidebar').classList.toggle('collapsed');
|
|
});
|
|
|
|
async function checkAuth() {
|
|
try {
|
|
const res = await fetch('backend/check_auth.php');
|
|
if (!res.ok) { window.location.href = 'login.html?redirect=poverty.html'; return; }
|
|
const data = await res.json();
|
|
document.getElementById('breadcrumb-text').textContent = 'Halo, ' + data.username;
|
|
document.getElementById('loader').style.display = 'none';
|
|
initMap();
|
|
} catch (err) { window.location.href = 'login.html?redirect=poverty.html'; }
|
|
}
|
|
|
|
document.getElementById('logout-btn').addEventListener('click', async () => {
|
|
await fetch('backend/logout.php');
|
|
window.location.href = 'index.html';
|
|
});
|
|
|
|
// Global variables
|
|
let ibadahData = []; let miskinData = [];
|
|
let ibadahLayer; let miskinLayer; let map;
|
|
let ibadahRadiuses = {}; let ibadahPoints = [];
|
|
let ibadahIcon, miskinRedIcon, miskinBlackIcon;
|
|
|
|
async function initMap() {
|
|
map = L.map('map').setView([-0.026, 109.342], 13);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }).addTo(map);
|
|
|
|
ibadahLayer = L.layerGroup().addTo(map);
|
|
miskinLayer = L.layerGroup().addTo(map);
|
|
|
|
ibadahIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] });
|
|
miskinRedIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] });
|
|
miskinBlackIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-black.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] });
|
|
|
|
try {
|
|
const [ibadahRes, miskinRes] = await Promise.all([ fetch('backend/read_ibadah.php'), fetch('backend/read_miskin.php') ]);
|
|
ibadahData = (await ibadahRes.json()).data || [];
|
|
miskinData = (await miskinRes.json()).data || [];
|
|
|
|
ibadahData.forEach(item => { ibadahRadiuses[item.id] = 0; });
|
|
renderMap();
|
|
} catch (err) { console.error("Error loading data"); }
|
|
}
|
|
|
|
window.updateRadius = function(id, value, lat, lng) {
|
|
ibadahRadiuses[id] = parseInt(value);
|
|
document.getElementById('radius-val-'+id).textContent = value;
|
|
renderBuffers();
|
|
renderMiskin();
|
|
};
|
|
|
|
function renderBuffers() {
|
|
map.eachLayer((layer) => { if (layer.options && layer.options.isBuffer) { map.removeLayer(layer); } });
|
|
ibadahData.forEach(item => {
|
|
const radius = ibadahRadiuses[item.id];
|
|
if (radius > 0) {
|
|
const lat = parseFloat(item.latitude), lng = parseFloat(item.longitude);
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
L.circle([lat, lng], { radius: radius, color: '#ef4444', fillColor: '#ef4444', fillOpacity: 0.1, weight: 2, isBuffer: true }).addTo(map);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderMap() { renderIbadah(); renderBuffers(); renderMiskin(); }
|
|
|
|
function renderIbadah() {
|
|
ibadahLayer.clearLayers(); ibadahPoints = [];
|
|
const filterIbadah = document.getElementById('search-ibadah').value.toLowerCase();
|
|
ibadahData.forEach(item => {
|
|
if (filterIbadah && !item.nama.toLowerCase().includes(filterIbadah)) return;
|
|
const lat = parseFloat(item.latitude), lng = parseFloat(item.longitude);
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
ibadahPoints.push({ id: item.id, pt: turf.point([lng, lat]) });
|
|
const popupContent = '<strong>' + item.nama + '</strong><br>' + item.alamat + '<br><span class="popup-radius-label">Atur Radius: <span id="radius-val-' + item.id + '">' + ibadahRadiuses[item.id] + '</span> m</span><input type="range" class="popup-slider" id="radius-slider-' + item.id + '" min="0" max="5000" step="100" value="' + ibadahRadiuses[item.id] + '" oninput="window.updateRadius(' + item.id + ', this.value, ' + lat + ', ' + lng + ')">';
|
|
L.marker([lat, lng], {icon: ibadahIcon}).bindPopup(popupContent).addTo(ibadahLayer);
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderMiskin() {
|
|
miskinLayer.clearLayers();
|
|
const filterMiskin = document.getElementById('search-miskin').value.toLowerCase();
|
|
miskinData.forEach(item => {
|
|
if (filterMiskin && !item.nama_kk.toLowerCase().includes(filterMiskin)) return;
|
|
const lat = parseFloat(item.latitude), lng = parseFloat(item.longitude);
|
|
if (isNaN(lat) || isNaN(lng)) return;
|
|
const miskinPt = turf.point([lng, lat]);
|
|
let isWithinRadius = false;
|
|
for (const ibadahObj of ibadahPoints) {
|
|
const currentRadiusKm = ibadahRadiuses[ibadahObj.id] / 1000;
|
|
if (currentRadiusKm > 0) {
|
|
const distance = turf.distance(miskinPt, ibadahObj.pt, {units: 'kilometers'});
|
|
if (distance <= currentRadiusKm) { isWithinRadius = true; break; }
|
|
}
|
|
}
|
|
const iconToUse = isWithinRadius ? miskinRedIcon : miskinBlackIcon;
|
|
L.marker([lat, lng], {icon: iconToUse}).bindPopup('<strong>KK: ' + item.nama_kk + '</strong><br>' + (item.keterangan || '-') + '<br>Status: ' + (isWithinRadius ? 'Masuk Area Radius' : 'Luar Area Radius')).addTo(miskinLayer);
|
|
});
|
|
}
|
|
|
|
document.getElementById('search-ibadah').addEventListener('input', renderIbadah);
|
|
document.getElementById('search-miskin').addEventListener('input', renderMiskin);
|
|
|
|
checkAuth();
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
fs.writeFileSync('poverty.html', html);
|
|
console.log("poverty.html updated");
|
|
}
|
|
|
|
buildSPBU();
|
|
buildPoverty();
|