first
This commit is contained in:
@@ -37,6 +37,36 @@ $statements = [
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
INDEX idx_penduduk_coords (latitude, longitude),
|
INDEX idx_penduduk_coords (latitude, longitude),
|
||||||
INDEX idx_penduduk_rumah_ibadah (rumah_ibadah_id)
|
INDEX idx_penduduk_rumah_ibadah (rumah_ibadah_id)
|
||||||
|
)",
|
||||||
|
"CREATE TABLE IF NOT EXISTS verifikasi_lapangan (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
jenis_target VARCHAR(50) NOT NULL DEFAULT 'penduduk_miskin',
|
||||||
|
target_nama VARCHAR(255) DEFAULT NULL,
|
||||||
|
petugas_nama VARCHAR(255) NOT NULL,
|
||||||
|
status_verifikasi VARCHAR(30) NOT NULL DEFAULT 'menunggu',
|
||||||
|
hasil_temuan TEXT,
|
||||||
|
tindak_lanjut TEXT,
|
||||||
|
foto VARCHAR(255) DEFAULT NULL,
|
||||||
|
latitude DECIMAL(10, 8) NOT NULL,
|
||||||
|
longitude DECIMAL(11, 8) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_verifikasi_coords (latitude, longitude),
|
||||||
|
INDEX idx_verifikasi_target (jenis_target, target_nama)
|
||||||
|
)",
|
||||||
|
"CREATE TABLE IF NOT EXISTS aduan_warga (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
kategori VARCHAR(100) NOT NULL,
|
||||||
|
pelapor_nama VARCHAR(255) NOT NULL,
|
||||||
|
kontak_pelapor VARCHAR(100) DEFAULT NULL,
|
||||||
|
deskripsi TEXT NOT NULL,
|
||||||
|
status_tindak_lanjut VARCHAR(30) NOT NULL DEFAULT 'baru',
|
||||||
|
tindak_lanjut TEXT,
|
||||||
|
foto VARCHAR(255) DEFAULT NULL,
|
||||||
|
latitude DECIMAL(10, 8) NOT NULL,
|
||||||
|
longitude DECIMAL(11, 8) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_aduan_coords (latitude, longitude),
|
||||||
|
INDEX idx_aduan_status (status_tindak_lanjut)
|
||||||
)"
|
)"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID aduan tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare('DELETE FROM aduan_warga WHERE id = ?');
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('i', $id);
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Aduan warga berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID verifikasi tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare('DELETE FROM verifikasi_lapangan WHERE id = ?');
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param('i', $id);
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Verifikasi lapangan berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success'=>true,'msg'=>'API placeholder untuk fitur jalan/parsel']);
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = $_POST['id'] ?? '';
|
||||||
|
|
||||||
|
if (empty($id)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID wajib diisi']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil data gambar untuk dihapus
|
||||||
|
$stmt = $conn->prepare("SELECT gambar FROM jalan_rusak WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
|
||||||
|
if ($row && $row['gambar'] && file_exists(__DIR__ . '/../' . $row['gambar'])) {
|
||||||
|
unlink(__DIR__ . '/../' . $row['gambar']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Hapus record
|
||||||
|
$stmt = $conn->prepare("DELETE FROM jalan_rusak WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Laporan jalan rusak berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, latitude, longitude, jenis_kerusakan, deskripsi, severity, gambar, created_at
|
||||||
|
FROM jalan_rusak
|
||||||
|
ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$row['latitude'] = (float)$row['latitude'];
|
||||||
|
$row['longitude'] = (float)$row['longitude'];
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Fitur Jalan & Parsel</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||||
|
<style>html,body,#map{height:100%;margin:0} .controls{position:absolute;top:8px;left:8px;z-index:400;background:#fff;padding:6px}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="controls">
|
||||||
|
<label>Tipe Jalan: <select id="tipe"><option value="nasional">Jalan Nasional</option><option value="provinsi">Jalan Provinsi</option><option value="kabupaten">Jalan Kabupaten</option></select></label>
|
||||||
|
</div>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||||
|
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const map = L.map('map').setView([-0.03,109.34],13);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||||
|
|
||||||
|
const drawnItems = new L.FeatureGroup().addTo(map);
|
||||||
|
const drawControl = new L.Control.Draw({ edit: { featureGroup: drawnItems } });
|
||||||
|
map.addControl(drawControl);
|
||||||
|
|
||||||
|
function styleByRoad(type){
|
||||||
|
if(type==='nasional') return {color:'#d62728',weight:5};
|
||||||
|
if(type==='provinsi') return {color:'#ff7f0e',weight:4};
|
||||||
|
return {color:'#2ca02c',weight:3};
|
||||||
|
}
|
||||||
|
|
||||||
|
map.on(L.Draw.Event.CREATED, function (e) {
|
||||||
|
const layer = e.layer;
|
||||||
|
if (layer instanceof L.Polyline && !(layer instanceof L.Polygon)){
|
||||||
|
const tipe = document.getElementById('tipe').value;
|
||||||
|
layer.setStyle(styleByRoad(tipe));
|
||||||
|
layer.properties = {type:'road', road_type:tipe};
|
||||||
|
} else if (layer instanceof L.Polygon){
|
||||||
|
const geo = layer.toGeoJSON();
|
||||||
|
const area = turf.area(geo); // in square meters
|
||||||
|
layer.properties = {type:'parsel', luas_m2: Math.round(area)};
|
||||||
|
layer.bindPopup('Parsel — Luas: '+Math.round(area)+' m²');
|
||||||
|
}
|
||||||
|
drawnItems.addLayer(layer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$latitude = $_POST['latitude'] ?? '';
|
||||||
|
$longitude = $_POST['longitude'] ?? '';
|
||||||
|
$jenis_kerusakan = $_POST['jenis_kerusakan'] ?? '';
|
||||||
|
$deskripsi = $_POST['deskripsi'] ?? '';
|
||||||
|
$severity = $_POST['severity'] ?? 'sedang';
|
||||||
|
|
||||||
|
if (empty($latitude) || empty($longitude) || empty($jenis_kerusakan)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Latitude, longitude, dan jenis kerusakan wajib diisi']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gambar = null;
|
||||||
|
|
||||||
|
// Handle file upload
|
||||||
|
if (isset($_FILES['gambar']) && $_FILES['gambar']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
|
||||||
|
$filename = $_FILES['gambar']['name'];
|
||||||
|
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Format gambar hanya JPG, PNG, GIF']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat folder uploads jika belum ada
|
||||||
|
if (!is_dir(__DIR__ . '/../uploads')) {
|
||||||
|
mkdir(__DIR__ . '/../uploads', 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate nama file unik
|
||||||
|
$gambar = 'uploads/' . time() . '_' . basename($filename);
|
||||||
|
|
||||||
|
if (!move_uploaded_file($_FILES['gambar']['tmp_name'], __DIR__ . '/../' . $gambar)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal upload gambar']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO jalan_rusak (latitude, longitude, jenis_kerusakan, deskripsi, severity, gambar) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("ddssss", $latitude, $longitude, $jenis_kerusakan, $deskripsi, $severity, $gambar);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Laporan jalan rusak berhasil disimpan', 'id' => $stmt->insert_id]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
$sql = 'SELECT id, kategori, pelapor_nama, kontak_pelapor, deskripsi, status_tindak_lanjut, tindak_lanjut, foto, latitude, longitude, created_at FROM aduan_warga ORDER BY created_at DESC';
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$row['latitude'] = (float)$row['latitude'];
|
||||||
|
$row['longitude'] = (float)$row['longitude'];
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
$sql = 'SELECT id, jenis_target, target_nama, petugas_nama, status_verifikasi, hasil_temuan, tindak_lanjut, foto, latitude, longitude, created_at FROM verifikasi_lapangan ORDER BY created_at DESC';
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$row['latitude'] = (float)$row['latitude'];
|
||||||
|
$row['longitude'] = (float)$row['longitude'];
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Simple loader to make Leaflet resources available globally to pages
|
||||||
|
(function(){
|
||||||
|
if(window._leafletLoaderRan) return; window._leafletLoaderRan = true;
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
||||||
|
script.defer = false;
|
||||||
|
script.onload = function(){
|
||||||
|
console.log('Leaflet loaded');
|
||||||
|
var ev = document.createEvent('Event'); ev.initEvent('leaflet:loaded', true, true); window.dispatchEvent(ev);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
})();
|
||||||
+748
-18
@@ -155,6 +155,12 @@
|
|||||||
<button class="icon-btn" id="modeRoadDamageBtn" title="Lapor jalan rusak">
|
<button class="icon-btn" id="modeRoadDamageBtn" title="Lapor jalan rusak">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 12h12M12 6v12M2 12h20M12 2v20"/><circle cx="12" cy="12" r="10"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 12h12M12 6v12M2 12h20M12 2v20"/><circle cx="12" cy="12" r="10"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="icon-btn" id="modeVerifikasiBtn" title="Verifikasi lapangan">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/><path d="M4 4h16v16H4z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="icon-btn" id="modeAduanWargaBtn" title="Aduan warga">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 8h10"/><path d="M7 12h6"/><path d="M20 16.5A2.5 2.5 0 0 1 17.5 19H8l-4 3V6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5z"/></svg>
|
||||||
|
</button>
|
||||||
<button class="icon-btn" id="modeChoroplethBtn" title="Lihat peta choropleth kecamatan">
|
<button class="icon-btn" id="modeChoroplethBtn" title="Lihat peta choropleth kecamatan">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v14"/><path d="M15 6v14"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v14"/><path d="M15 6v14"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -251,6 +257,12 @@
|
|||||||
<button class="icon-btn" id="miniModePendudukMiskinBtn" title="Input penduduk miskin">
|
<button class="icon-btn" id="miniModePendudukMiskinBtn" title="Input penduduk miskin">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4z"/><path d="M4 21a8 8 0 0 1 16 0"/><path d="M18 8h4"/><path d="M20 6v4"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4z"/><path d="M4 21a8 8 0 0 1 16 0"/><path d="M18 8h4"/><path d="M20 6v4"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="icon-btn" id="miniModeVerifikasiBtn" title="Verifikasi lapangan">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/><path d="M4 4h16v16H4z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="icon-btn" id="miniModeAduanWargaBtn" title="Aduan warga">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 8h10"/><path d="M7 12h6"/><path d="M20 16.5A2.5 2.5 0 0 1 17.5 19H8l-4 3V6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5z"/></svg>
|
||||||
|
</button>
|
||||||
<button class="icon-btn" id="toggleRegistryDrawerBtn" title="Buka registry & filter">
|
<button class="icon-btn" id="toggleRegistryDrawerBtn" title="Buka registry & filter">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -401,6 +413,14 @@
|
|||||||
<input id="layerToggle-pendudukMiskinLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
<input id="layerToggle-pendudukMiskinLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||||
<span>Penduduk Miskin</span>
|
<span>Penduduk Miskin</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||||
|
<input id="layerToggle-verifikasiLapanganLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||||
|
<span>Verifikasi Lapangan</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||||
|
<input id="layerToggle-aduanWargaLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||||
|
<span>Aduan Warga</span>
|
||||||
|
</label>
|
||||||
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||||
<input id="layerToggle-roadNetworkLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
<input id="layerToggle-roadNetworkLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||||
<span>Jalan Eksternal</span>
|
<span>Jalan Eksternal</span>
|
||||||
@@ -644,6 +664,128 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="verifikasiLapanganModal" class="fixed inset-0 z-[10005] hidden items-center justify-center bg-slate-900/50 px-4">
|
||||||
|
<div class="w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl">
|
||||||
|
<div class="flex items-start justify-between gap-4 border-b pb-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold text-slate-800">Verifikasi Lapangan</h3>
|
||||||
|
<p class="text-sm text-slate-500">Catat hasil cek lapangan untuk data kemiskinan atau infrastruktur.</p>
|
||||||
|
</div>
|
||||||
|
<button id="closeVerifikasiLapanganModalBtn" class="rounded-lg bg-slate-100 px-3 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Tutup</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||||
|
<input id="verifikasiLapanganId" type="hidden">
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Target Data</label>
|
||||||
|
<select id="verifikasiJenisTarget" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||||
|
<option value="penduduk_miskin">Penduduk Miskin</option>
|
||||||
|
<option value="jalan_rusak">Jalan Rusak</option>
|
||||||
|
<option value="rumah_ibadah">Rumah Ibadah</option>
|
||||||
|
<option value="lainnya">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Target</label>
|
||||||
|
<input id="verifikasiTargetNama" type="text" placeholder="Contoh: KK A / Jalan RW 03" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Petugas</label>
|
||||||
|
<input id="verifikasiPetugasNama" type="text" placeholder="Nama surveyor" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Status</label>
|
||||||
|
<select id="verifikasiStatus" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||||
|
<option value="menunggu">Menunggu</option>
|
||||||
|
<option value="valid">Valid</option>
|
||||||
|
<option value="perlu_tindak_lanjut">Perlu Tindak Lanjut</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Hasil Temuan</label>
|
||||||
|
<textarea id="verifikasiHasilTemuan" rows="3" placeholder="Ringkasan hasil cek lapangan" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Tindak Lanjut</label>
|
||||||
|
<textarea id="verifikasiTindakLanjut" rows="2" placeholder="Catatan tindak lanjut" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Foto (Opsional)</label>
|
||||||
|
<input id="verifikasiFoto" type="file" accept="image/*" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2 text-[10px] text-slate-500 bg-slate-50 p-2 rounded">
|
||||||
|
📍 Koordinat: <span id="verifikasiCoords">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex flex-wrap gap-2 border-t pt-4">
|
||||||
|
<button id="saveVerifikasiLapanganBtn" class="rounded-lg bg-emerald-600 px-4 py-2 text-sm font-bold text-white hover:bg-emerald-700">Simpan Verifikasi</button>
|
||||||
|
<button id="cancelVerifikasiLapanganBtn" class="rounded-lg bg-slate-100 px-4 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Batal</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="aduanWargaModal" class="fixed inset-0 z-[10006] hidden items-center justify-center bg-slate-900/50 px-4">
|
||||||
|
<div class="w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl">
|
||||||
|
<div class="flex items-start justify-between gap-4 border-b pb-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold text-slate-800">Aduan Warga</h3>
|
||||||
|
<p class="text-sm text-slate-500">Terima aduan berbasis lokasi untuk memprioritaskan respon lapangan.</p>
|
||||||
|
</div>
|
||||||
|
<button id="closeAduanWargaModalBtn" class="rounded-lg bg-slate-100 px-3 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Tutup</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||||
|
<input id="aduanWargaId" type="hidden">
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Kategori</label>
|
||||||
|
<select id="aduanKategori" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||||
|
<option value="">Pilih kategori</option>
|
||||||
|
<option value="jalan rusak">Jalan Rusak</option>
|
||||||
|
<option value="banjir">Banjir</option>
|
||||||
|
<option value="rumah tidak layak">Rumah Tidak Layak</option>
|
||||||
|
<option value="air bersih">Air Bersih</option>
|
||||||
|
<option value="sanitasi">Sanitasi</option>
|
||||||
|
<option value="akses bantuan">Akses Bantuan</option>
|
||||||
|
<option value="lainnya">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Pelapor</label>
|
||||||
|
<input id="aduanPelaporNama" type="text" placeholder="Nama warga" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Kontak</label>
|
||||||
|
<input id="aduanKontakPelapor" type="text" placeholder="Nomor HP / WA" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Status</label>
|
||||||
|
<select id="aduanStatus" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||||
|
<option value="baru">Baru</option>
|
||||||
|
<option value="diproses">Diproses</option>
|
||||||
|
<option value="selesai">Selesai</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Deskripsi Aduan</label>
|
||||||
|
<textarea id="aduanDeskripsi" rows="3" placeholder="Jelaskan kondisi yang diadukan" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Tindak Lanjut</label>
|
||||||
|
<textarea id="aduanTindakLanjut" rows="2" placeholder="Catatan respon / tindak lanjut" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Foto (Opsional)</label>
|
||||||
|
<input id="aduanFoto" type="file" accept="image/*" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-2 text-[10px] text-slate-500 bg-slate-50 p-2 rounded">
|
||||||
|
📍 Koordinat: <span id="aduanCoords">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 flex flex-wrap gap-2 border-t pt-4">
|
||||||
|
<button id="saveAduanWargaBtn" class="rounded-lg bg-rose-600 px-4 py-2 text-sm font-bold text-white hover:bg-rose-700">Simpan Aduan</button>
|
||||||
|
<button id="cancelAduanWargaBtn" class="rounded-lg bg-slate-100 px-4 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Batal</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<select id="featureType" class="hidden">
|
<select id="featureType" class="hidden">
|
||||||
<option value="polyline">polyline</option>
|
<option value="polyline">polyline</option>
|
||||||
<option value="polygon">polygon</option>
|
<option value="polygon">polygon</option>
|
||||||
@@ -687,12 +829,20 @@
|
|||||||
let utilityControl = null;
|
let utilityControl = null;
|
||||||
let rumahIbadahLayer = L.featureGroup().addTo(map);
|
let rumahIbadahLayer = L.featureGroup().addTo(map);
|
||||||
let pendudukMiskinLayer = L.featureGroup().addTo(map);
|
let pendudukMiskinLayer = L.featureGroup().addTo(map);
|
||||||
|
let verifikasiLapanganLayer = L.featureGroup().addTo(map);
|
||||||
|
let aduanWargaLayer = L.featureGroup().addTo(map);
|
||||||
let rumahIbadahLayerMap = {};
|
let rumahIbadahLayerMap = {};
|
||||||
let pendudukMiskinLayerMap = {};
|
let pendudukMiskinLayerMap = {};
|
||||||
|
let verifikasiLapanganLayerMap = {};
|
||||||
|
let aduanWargaLayerMap = {};
|
||||||
let rumahIbadahRecords = [];
|
let rumahIbadahRecords = [];
|
||||||
let pendudukMiskinRecords = [];
|
let pendudukMiskinRecords = [];
|
||||||
|
let verifikasiLapanganRecords = [];
|
||||||
|
let aduanWargaRecords = [];
|
||||||
let pendingRumahIbadahCoords = null;
|
let pendingRumahIbadahCoords = null;
|
||||||
let pendingPendudukMiskinCoords = null;
|
let pendingPendudukMiskinCoords = null;
|
||||||
|
let pendingVerifikasiCoords = null;
|
||||||
|
let pendingAduanCoords = null;
|
||||||
let editingRumahIbadahId = null;
|
let editingRumahIbadahId = null;
|
||||||
let selectedRumahIbadahId = null;
|
let selectedRumahIbadahId = null;
|
||||||
let selectedBantuanRumahIbadahId = null;
|
let selectedBantuanRumahIbadahId = null;
|
||||||
@@ -1151,7 +1301,29 @@
|
|||||||
longitude: Number(record.longitude)
|
longitude: Number(record.longitude)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return [...pointRecords, ...spatialRecords, ...roadDamageRegistryRecords, ...rumahIbadahRegistryRecords, ...pendudukMiskinRegistryRecords].sort((left, right) => {
|
const verifikasiRegistryRecords = verifikasiLapanganRecords.map(record => ({
|
||||||
|
id: Number(record.id),
|
||||||
|
type: 'verifikasiLapangan',
|
||||||
|
title: record.target_nama || 'Verifikasi lapangan',
|
||||||
|
subtitle: record.petugas_nama || '-',
|
||||||
|
status: record.status_verifikasi || 'menunggu',
|
||||||
|
created_at: record.created_at || null,
|
||||||
|
latitude: Number(record.latitude),
|
||||||
|
longitude: Number(record.longitude)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const aduanRegistryRecords = aduanWargaRecords.map(record => ({
|
||||||
|
id: Number(record.id),
|
||||||
|
type: 'aduanWarga',
|
||||||
|
title: record.kategori || 'Aduan warga',
|
||||||
|
subtitle: record.pelapor_nama || '-',
|
||||||
|
status: record.status_tindak_lanjut || 'baru',
|
||||||
|
created_at: record.created_at || null,
|
||||||
|
latitude: Number(record.latitude),
|
||||||
|
longitude: Number(record.longitude)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...pointRecords, ...spatialRecords, ...roadDamageRegistryRecords, ...rumahIbadahRegistryRecords, ...pendudukMiskinRegistryRecords, ...verifikasiRegistryRecords, ...aduanRegistryRecords].sort((left, right) => {
|
||||||
const leftDate = parseDateOnly(left.created_at);
|
const leftDate = parseDateOnly(left.created_at);
|
||||||
const rightDate = parseDateOnly(right.created_at);
|
const rightDate = parseDateOnly(right.created_at);
|
||||||
|
|
||||||
@@ -1169,6 +1341,8 @@
|
|||||||
if (type === 'roadDamage') return 'Jalan Rusak';
|
if (type === 'roadDamage') return 'Jalan Rusak';
|
||||||
if (type === 'rumahIbadah') return 'Rumah Ibadah';
|
if (type === 'rumahIbadah') return 'Rumah Ibadah';
|
||||||
if (type === 'pendudukMiskin') return 'Penduduk Miskin';
|
if (type === 'pendudukMiskin') return 'Penduduk Miskin';
|
||||||
|
if (type === 'verifikasiLapangan') return 'Verifikasi Lapangan';
|
||||||
|
if (type === 'aduanWarga') return 'Aduan Warga';
|
||||||
return 'Data';
|
return 'Data';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1179,6 +1353,8 @@
|
|||||||
if (type === 'roadDamage') return 'bg-red-100 text-red-700';
|
if (type === 'roadDamage') return 'bg-red-100 text-red-700';
|
||||||
if (type === 'rumahIbadah') return 'bg-teal-100 text-teal-700';
|
if (type === 'rumahIbadah') return 'bg-teal-100 text-teal-700';
|
||||||
if (type === 'pendudukMiskin') return 'bg-rose-100 text-rose-700';
|
if (type === 'pendudukMiskin') return 'bg-rose-100 text-rose-700';
|
||||||
|
if (type === 'verifikasiLapangan') return 'bg-emerald-100 text-emerald-700';
|
||||||
|
if (type === 'aduanWarga') return 'bg-fuchsia-100 text-fuchsia-700';
|
||||||
return 'bg-slate-100 text-slate-700';
|
return 'bg-slate-100 text-slate-700';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1228,6 +1404,22 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'verifikasiLapangan') {
|
||||||
|
const marker = verifikasiLapanganLayerMap[id];
|
||||||
|
if (!marker) return;
|
||||||
|
map.setView(marker.getLatLng(), Math.max(map.getZoom(), 16));
|
||||||
|
marker.openPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'aduanWarga') {
|
||||||
|
const marker = aduanWargaLayerMap[id];
|
||||||
|
if (!marker) return;
|
||||||
|
map.setView(marker.getLatLng(), Math.max(map.getZoom(), 16));
|
||||||
|
marker.openPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const layer = featureLayerMap[id];
|
const layer = featureLayerMap[id];
|
||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
if (layer.getBounds) {
|
if (layer.getBounds) {
|
||||||
@@ -1267,7 +1459,11 @@
|
|||||||
? `PIC: ${record.subtitle}`
|
? `PIC: ${record.subtitle}`
|
||||||
: record.type === 'pendudukMiskin'
|
: record.type === 'pendudukMiskin'
|
||||||
? `Anggota: ${record.subtitle}`
|
? `Anggota: ${record.subtitle}`
|
||||||
: `Kategori: ${record.subtitle}`;
|
: record.type === 'verifikasiLapangan'
|
||||||
|
? `Petugas: ${record.subtitle}`
|
||||||
|
: record.type === 'aduanWarga'
|
||||||
|
? `Pelapor: ${record.subtitle}`
|
||||||
|
: `Kategori: ${record.subtitle}`;
|
||||||
const statusLabel = record.type === 'point'
|
const statusLabel = record.type === 'point'
|
||||||
? `24 Jam: ${record.status}`
|
? `24 Jam: ${record.status}`
|
||||||
: record.type === 'roadDamage'
|
: record.type === 'roadDamage'
|
||||||
@@ -1276,7 +1472,11 @@
|
|||||||
? `Radius: ${record.status}`
|
? `Radius: ${record.status}`
|
||||||
: record.type === 'pendudukMiskin'
|
: record.type === 'pendudukMiskin'
|
||||||
? `Rumah Ibadah: ${record.status}`
|
? `Rumah Ibadah: ${record.status}`
|
||||||
: `Status: ${record.status}`;
|
: record.type === 'verifikasiLapangan'
|
||||||
|
? `Status Verifikasi: ${record.status}`
|
||||||
|
: record.type === 'aduanWarga'
|
||||||
|
? `Status Tindak Lanjut: ${record.status}`
|
||||||
|
: `Status: ${record.status}`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<button type="button" onclick="focusRegistryItem('${record.type}', ${record.id})" class="registry-item w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-left shadow-sm">
|
<button type="button" onclick="focusRegistryItem('${record.type}', ${record.id})" class="registry-item w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-left shadow-sm">
|
||||||
@@ -1342,6 +1542,8 @@
|
|||||||
roadDamageLayer: document.getElementById('layerToggle-roadDamageLayer'),
|
roadDamageLayer: document.getElementById('layerToggle-roadDamageLayer'),
|
||||||
rumahIbadahLayer: document.getElementById('layerToggle-rumahIbadahLayer'),
|
rumahIbadahLayer: document.getElementById('layerToggle-rumahIbadahLayer'),
|
||||||
pendudukMiskinLayer: document.getElementById('layerToggle-pendudukMiskinLayer'),
|
pendudukMiskinLayer: document.getElementById('layerToggle-pendudukMiskinLayer'),
|
||||||
|
verifikasiLapanganLayer: document.getElementById('layerToggle-verifikasiLapanganLayer'),
|
||||||
|
aduanWargaLayer: document.getElementById('layerToggle-aduanWargaLayer'),
|
||||||
roadNetworkLayer: document.getElementById('layerToggle-roadNetworkLayer')
|
roadNetworkLayer: document.getElementById('layerToggle-roadNetworkLayer')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1356,6 +1558,8 @@
|
|||||||
{ key: 'roadDamageLayer', layer: roadDamageLayer, label: 'Jalan Rusak' },
|
{ key: 'roadDamageLayer', layer: roadDamageLayer, label: 'Jalan Rusak' },
|
||||||
{ key: 'rumahIbadahLayer', layer: rumahIbadahLayer, label: 'Rumah Ibadah' },
|
{ key: 'rumahIbadahLayer', layer: rumahIbadahLayer, label: 'Rumah Ibadah' },
|
||||||
{ key: 'pendudukMiskinLayer', layer: pendudukMiskinLayer, label: 'Penduduk Miskin' },
|
{ key: 'pendudukMiskinLayer', layer: pendudukMiskinLayer, label: 'Penduduk Miskin' },
|
||||||
|
{ key: 'verifikasiLapanganLayer', layer: verifikasiLapanganLayer, label: 'Verifikasi Lapangan' },
|
||||||
|
{ key: 'aduanWargaLayer', layer: aduanWargaLayer, label: 'Aduan Warga' },
|
||||||
{ key: 'roadNetworkLayer', layer: roadNetworkLayer, label: 'Jalan Eksternal' }
|
{ key: 'roadNetworkLayer', layer: roadNetworkLayer, label: 'Jalan Eksternal' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1503,6 +1707,11 @@
|
|||||||
const ui = getBantuanUi();
|
const ui = getBantuanUi();
|
||||||
if (!ui.panel || !ui.list || !ui.count || !ui.rumahFilter || !ui.statusFilter || !ui.search) return;
|
if (!ui.panel || !ui.list || !ui.count || !ui.rumahFilter || !ui.statusFilter || !ui.search) return;
|
||||||
|
|
||||||
|
const existingSummary = ui.panel.querySelector('[data-bantuan-summary="1"]');
|
||||||
|
if (existingSummary) {
|
||||||
|
existingSummary.remove();
|
||||||
|
}
|
||||||
|
|
||||||
const previousValue = ui.rumahFilter.value;
|
const previousValue = ui.rumahFilter.value;
|
||||||
const options = ['<option value="">Pilih rumah ibadah...</option>'].concat(
|
const options = ['<option value="">Pilih rumah ibadah...</option>'].concat(
|
||||||
rumahIbadahRecords.map(record => `<option value="${Number(record.id)}">${record.nama} (radius ${Number(record.radius_m || 0).toLocaleString('id-ID')} m)</option>`)
|
rumahIbadahRecords.map(record => `<option value="${Number(record.id)}">${record.nama} (radius ${Number(record.radius_m || 0).toLocaleString('id-ID')} m)</option>`)
|
||||||
@@ -1545,8 +1754,34 @@
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusCounts = residents.reduce((accumulator, item) => {
|
||||||
|
const status = normalizeAidStatus(item.bantuan_status);
|
||||||
|
accumulator[status] = (accumulator[status] || 0) + 1;
|
||||||
|
return accumulator;
|
||||||
|
}, { belum: 0, proses: 0, sudah: 0 });
|
||||||
|
|
||||||
ui.count.innerText = String(residents.length);
|
ui.count.innerText = String(residents.length);
|
||||||
|
|
||||||
|
const summaryWrapper = document.createElement('div');
|
||||||
|
summaryWrapper.setAttribute('data-bantuan-summary', '1');
|
||||||
|
summaryWrapper.className = 'mt-3 grid grid-cols-3 gap-2 text-[11px] text-slate-600';
|
||||||
|
summaryWrapper.innerHTML = `
|
||||||
|
<div class="rounded-lg bg-rose-50 px-2 py-2 text-center">
|
||||||
|
<div class="font-black text-rose-700">${statusCounts.belum || 0}</div>
|
||||||
|
<div>Belum</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-amber-50 px-2 py-2 text-center">
|
||||||
|
<div class="font-black text-amber-700">${statusCounts.proses || 0}</div>
|
||||||
|
<div>Diproses</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-emerald-50 px-2 py-2 text-center">
|
||||||
|
<div class="font-black text-emerald-700">${statusCounts.sudah || 0}</div>
|
||||||
|
<div>Sudah</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
ui.list.parentNode.insertBefore(summaryWrapper, ui.list);
|
||||||
|
|
||||||
if (!residents.length) {
|
if (!residents.length) {
|
||||||
ui.list.innerHTML = `
|
ui.list.innerHTML = `
|
||||||
<div class="rounded-xl border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-sm text-slate-500">
|
<div class="rounded-xl border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-sm text-slate-500">
|
||||||
@@ -1681,11 +1916,15 @@
|
|||||||
pendudukMiskin: document.getElementById('modePendudukMiskinBtn'),
|
pendudukMiskin: document.getElementById('modePendudukMiskinBtn'),
|
||||||
choropleth: document.getElementById('modeChoroplethBtn'),
|
choropleth: document.getElementById('modeChoroplethBtn'),
|
||||||
roadDamage: document.getElementById('leafletRoadDamageBtn') || document.getElementById('modeRoadDamageBtn'),
|
roadDamage: document.getElementById('leafletRoadDamageBtn') || document.getElementById('modeRoadDamageBtn'),
|
||||||
|
verifikasiLapangan: document.getElementById('leafletVerifikasiBtn') || document.getElementById('modeVerifikasiBtn'),
|
||||||
|
aduanWarga: document.getElementById('leafletAduanWargaBtn') || document.getElementById('modeAduanWargaBtn'),
|
||||||
miniPoint: document.getElementById('miniModePointBtn'),
|
miniPoint: document.getElementById('miniModePointBtn'),
|
||||||
miniPolyline: document.getElementById('miniModePolylineBtn'),
|
miniPolyline: document.getElementById('miniModePolylineBtn'),
|
||||||
miniPolygon: document.getElementById('miniModePolygonBtn'),
|
miniPolygon: document.getElementById('miniModePolygonBtn'),
|
||||||
miniRumahIbadah: document.getElementById('miniModeRumahIbadahBtn'),
|
miniRumahIbadah: document.getElementById('miniModeRumahIbadahBtn'),
|
||||||
miniPendudukMiskin: document.getElementById('miniModePendudukMiskinBtn')
|
miniPendudukMiskin: document.getElementById('miniModePendudukMiskinBtn'),
|
||||||
|
miniVerifikasiLapangan: document.getElementById('miniModeVerifikasiBtn'),
|
||||||
|
miniAduanWarga: document.getElementById('miniModeAduanWargaBtn')
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.values(modeButtons).forEach(button => {
|
Object.values(modeButtons).forEach(button => {
|
||||||
@@ -1711,6 +1950,14 @@
|
|||||||
if (modeButtons.pendudukMiskin) modeButtons.pendudukMiskin.classList.add('active');
|
if (modeButtons.pendudukMiskin) modeButtons.pendudukMiskin.classList.add('active');
|
||||||
if (modeButtons.miniPendudukMiskin) modeButtons.miniPendudukMiskin.classList.add('active');
|
if (modeButtons.miniPendudukMiskin) modeButtons.miniPendudukMiskin.classList.add('active');
|
||||||
}
|
}
|
||||||
|
if (mode === 'verifikasiLapangan') {
|
||||||
|
if (modeButtons.verifikasiLapangan) modeButtons.verifikasiLapangan.classList.add('active');
|
||||||
|
if (modeButtons.miniVerifikasiLapangan) modeButtons.miniVerifikasiLapangan.classList.add('active');
|
||||||
|
}
|
||||||
|
if (mode === 'aduanWarga') {
|
||||||
|
if (modeButtons.aduanWarga) modeButtons.aduanWarga.classList.add('active');
|
||||||
|
if (modeButtons.miniAduanWarga) modeButtons.miniAduanWarga.classList.add('active');
|
||||||
|
}
|
||||||
if (modeButtons[mode]) {
|
if (modeButtons[mode]) {
|
||||||
modeButtons[mode].classList.add('active');
|
modeButtons[mode].classList.add('active');
|
||||||
}
|
}
|
||||||
@@ -1737,14 +1984,16 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'roadDamage') {
|
if (mode === 'roadDamage' || mode === 'verifikasiLapangan' || mode === 'aduanWarga') {
|
||||||
spatialControls.classList.add('hidden');
|
spatialControls.classList.add('hidden');
|
||||||
runActionBtn.innerText = 'Klik Peta';
|
runActionBtn.innerText = 'Klik Peta';
|
||||||
runActionBtn.disabled = true;
|
runActionBtn.disabled = true;
|
||||||
clearDraftBtn.disabled = true;
|
clearDraftBtn.disabled = true;
|
||||||
updateDrawControl(null);
|
updateDrawControl(null);
|
||||||
document.getElementById('modeMeasure').value = '';
|
document.getElementById('modeMeasure').value = '';
|
||||||
loadRoadDamage(true);
|
if (mode === 'roadDamage') loadRoadDamage(true);
|
||||||
|
if (mode === 'verifikasiLapangan') loadVerifikasiLapangan(true);
|
||||||
|
if (mode === 'aduanWarga') loadAduanWarga(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2376,6 +2625,10 @@
|
|||||||
<button onclick="deletePendudukMiskin(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
<button onclick="deletePendudukMiskin(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||||
<button onclick="focusRegistryItem('rumahIbadah', ${record.rumah_ibadah_id || 0})" class="rounded-lg bg-emerald-50 px-3 py-2 text-xs font-bold text-emerald-700 hover:bg-emerald-100">Lihat Rumah Ibadah</button>
|
<button onclick="focusRegistryItem('rumahIbadah', ${record.rumah_ibadah_id || 0})" class="rounded-lg bg-emerald-50 px-3 py-2 text-xs font-bold text-emerald-700 hover:bg-emerald-100">Lihat Rumah Ibadah</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2 grid grid-cols-2 gap-2">
|
||||||
|
<button onclick="openVerifikasiFromPenduduk(${record.id})" class="rounded-lg bg-emerald-50 px-3 py-2 text-xs font-bold text-emerald-700 hover:bg-emerald-100">Verifikasi</button>
|
||||||
|
<button onclick="setDataMode('aduanWarga'); openAduanWargaModal(${Number(record.latitude)}, ${Number(record.longitude)})" class="rounded-lg bg-fuchsia-50 px-3 py-2 text-xs font-bold text-fuchsia-700 hover:bg-fuchsia-100">Buat Aduan</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -2459,6 +2712,329 @@
|
|||||||
showMessage(`Menampilkan ${withinRadius.length} lokasi miskin terdekat`, 'bg-emerald-600 text-white');
|
showMessage(`Menampilkan ${withinRadius.length} lokasi miskin terdekat`, 'bg-emerald-600 text-white');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAidStatusColor(status) {
|
||||||
|
const normalized = normalizeAidStatus(status);
|
||||||
|
if (normalized === 'sudah') return '#10b981';
|
||||||
|
if (normalized === 'proses') return '#f59e0b';
|
||||||
|
return '#dc2626';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPendudukMiskinIcon(status) {
|
||||||
|
const color = getAidStatusColor(status);
|
||||||
|
return L.divIcon({
|
||||||
|
className: 'custom-marker',
|
||||||
|
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="8" r="4"/><path d="M4 22a8 8 0 0 1 16 0"/></svg>`,
|
||||||
|
iconSize: [32, 40],
|
||||||
|
iconAnchor: [16, 40],
|
||||||
|
popupAnchor: [0, -40]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVerifikasiStatusBadge(status) {
|
||||||
|
const value = String(status || 'menunggu').toLowerCase().trim();
|
||||||
|
if (value === 'valid') return { label: 'Valid', className: 'bg-emerald-100 text-emerald-700' };
|
||||||
|
if (value === 'perlu_tindak_lanjut') return { label: 'Perlu Tindak Lanjut', className: 'bg-amber-100 text-amber-700' };
|
||||||
|
return { label: 'Menunggu', className: 'bg-slate-100 text-slate-700' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVerifikasiIcon(status) {
|
||||||
|
const value = String(status || 'menunggu').toLowerCase().trim();
|
||||||
|
const color = value === 'valid' ? '#10b981' : value === 'perlu_tindak_lanjut' ? '#f59e0b' : '#64748b';
|
||||||
|
return L.divIcon({
|
||||||
|
className: 'custom-marker',
|
||||||
|
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><path d="M12 0 3 4v6c0 6.2 4.2 11.4 9 14 4.8-2.6 9-7.8 9-14V4z"/><path d="M9.5 11.5 11 13l3.5-4" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
|
||||||
|
iconSize: [32, 40],
|
||||||
|
iconAnchor: [16, 40],
|
||||||
|
popupAnchor: [0, -40]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAduanStatusBadge(status) {
|
||||||
|
const value = String(status || 'baru').toLowerCase().trim();
|
||||||
|
if (value === 'selesai') return { label: 'Selesai', className: 'bg-emerald-100 text-emerald-700' };
|
||||||
|
if (value === 'diproses') return { label: 'Diproses', className: 'bg-amber-100 text-amber-700' };
|
||||||
|
return { label: 'Baru', className: 'bg-rose-100 text-rose-700' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAduanIcon(status) {
|
||||||
|
const value = String(status || 'baru').toLowerCase().trim();
|
||||||
|
const color = value === 'selesai' ? '#10b981' : value === 'diproses' ? '#f59e0b' : '#e11d48';
|
||||||
|
return L.divIcon({
|
||||||
|
className: 'custom-marker',
|
||||||
|
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-5 0-9 3.6-9 8 0 3.1 2.1 5.8 5.2 7l-.7 4 4.5-2.4c.3 0 .6.1 1 .1 5 0 9-3.6 9-8s-4-8.7-9-8.7z"/><circle cx="9" cy="10" r="1" fill="white"/><circle cx="12" cy="10" r="1" fill="white"/><circle cx="15" cy="10" r="1" fill="white"/></svg>`,
|
||||||
|
iconSize: [32, 40],
|
||||||
|
iconAnchor: [16, 40],
|
||||||
|
popupAnchor: [0, -40]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVerifikasiLapanganPopup(record) {
|
||||||
|
const badge = getVerifikasiStatusBadge(record.status_verifikasi);
|
||||||
|
const fotoHtml = record.foto ? `<img src="${record.foto}" alt="Foto verifikasi" class="mt-2 w-full rounded-lg max-h-48 object-cover">` : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="w-full p-4">
|
||||||
|
<h3 class="text-lg font-bold text-slate-800">Verifikasi Lapangan</h3>
|
||||||
|
<p class="mt-1 text-sm text-slate-600">Target: ${record.target_nama || '-'} (${record.jenis_target || '-'})</p>
|
||||||
|
<p class="mt-1 text-sm text-slate-600">Petugas: ${record.petugas_nama || '-'}</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="registry-chip ${badge.className}">${badge.label}</span>
|
||||||
|
</div>
|
||||||
|
${record.hasil_temuan ? `<p class="mt-2 text-sm text-slate-600"><strong>Temuan:</strong> ${record.hasil_temuan}</p>` : ''}
|
||||||
|
${record.tindak_lanjut ? `<p class="mt-1 text-sm text-slate-600"><strong>Tindak lanjut:</strong> ${record.tindak_lanjut}</p>` : ''}
|
||||||
|
<p class="mt-1 text-xs text-slate-500">📍 ${Number(record.latitude).toFixed(5)}, ${Number(record.longitude).toFixed(5)}</p>
|
||||||
|
${fotoHtml}
|
||||||
|
<div class="mt-3 grid grid-cols-2 gap-2 border-t pt-3">
|
||||||
|
<button onclick="openVerifikasiLapanganEditor(${record.id})" class="rounded-lg bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700 hover:bg-indigo-100">Edit</button>
|
||||||
|
<button onclick="deleteVerifikasiLapangan(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openVerifikasiLapanganModal(lat, lng, record = null) {
|
||||||
|
pendingVerifikasiCoords = { lat, lng };
|
||||||
|
document.getElementById('verifikasiLapanganId').value = record ? String(record.id) : '';
|
||||||
|
document.getElementById('verifikasiJenisTarget').value = record ? (record.jenis_target || 'penduduk_miskin') : 'penduduk_miskin';
|
||||||
|
document.getElementById('verifikasiTargetNama').value = record ? (record.target_nama || '') : '';
|
||||||
|
document.getElementById('verifikasiPetugasNama').value = record ? (record.petugas_nama || '') : '';
|
||||||
|
document.getElementById('verifikasiStatus').value = record ? (record.status_verifikasi || 'menunggu') : 'menunggu';
|
||||||
|
document.getElementById('verifikasiHasilTemuan').value = record ? (record.hasil_temuan || '') : '';
|
||||||
|
document.getElementById('verifikasiTindakLanjut').value = record ? (record.tindak_lanjut || '') : '';
|
||||||
|
document.getElementById('verifikasiCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||||
|
document.getElementById('verifikasiFoto').value = '';
|
||||||
|
|
||||||
|
const modal = document.getElementById('verifikasiLapanganModal');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
suppressAutoReload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeVerifikasiLapanganModal() {
|
||||||
|
const modal = document.getElementById('verifikasiLapanganModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
pendingVerifikasiCoords = null;
|
||||||
|
suppressAutoReload = !!document.querySelector('[id^="edit-mode-"]:not(.hidden)') || !!editingFeatureId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveVerifikasiLapangan() {
|
||||||
|
if (!pendingVerifikasiCoords) {
|
||||||
|
showMessage('Koordinat verifikasi tidak ditemukan', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = document.getElementById('verifikasiLapanganId').value;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('jenis_target', document.getElementById('verifikasiJenisTarget').value);
|
||||||
|
formData.append('target_nama', document.getElementById('verifikasiTargetNama').value.trim());
|
||||||
|
formData.append('petugas_nama', document.getElementById('verifikasiPetugasNama').value.trim());
|
||||||
|
formData.append('status_verifikasi', document.getElementById('verifikasiStatus').value);
|
||||||
|
formData.append('hasil_temuan', document.getElementById('verifikasiHasilTemuan').value.trim());
|
||||||
|
formData.append('tindak_lanjut', document.getElementById('verifikasiTindakLanjut').value.trim());
|
||||||
|
formData.append('latitude', pendingVerifikasiCoords.lat);
|
||||||
|
formData.append('longitude', pendingVerifikasiCoords.lng);
|
||||||
|
|
||||||
|
const fileInput = document.getElementById('verifikasiFoto');
|
||||||
|
if (fileInput.files && fileInput.files[0]) {
|
||||||
|
formData.append('foto', fileInput.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = id ? 'update_verifikasi_lapangan.php' : 'save_verifikasi_lapangan.php';
|
||||||
|
if (id) {
|
||||||
|
formData.append('id', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(endpoint, { method: 'POST', body: formData })
|
||||||
|
.then(res => parseJsonResponse(res, endpoint))
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showMessage('✅ Verifikasi lapangan berhasil disimpan', 'bg-green-600 text-white');
|
||||||
|
closeVerifikasiLapanganModal();
|
||||||
|
loadVerifikasiLapangan(true);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Gagal menyimpan verifikasi');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showMessage(err.message || 'Gagal menyimpan verifikasi', 'bg-red-600 text-white'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteVerifikasiLapangan(id) {
|
||||||
|
if (!confirm('Hapus verifikasi lapangan ini?')) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
|
||||||
|
fetch('delete_verifikasi_lapangan.php', { method: 'POST', body: formData })
|
||||||
|
.then(res => parseJsonResponse(res, 'delete_verifikasi_lapangan.php'))
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showMessage('🗑️ Verifikasi lapangan berhasil dihapus', 'bg-orange-600 text-white');
|
||||||
|
loadVerifikasiLapangan(true);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Gagal menghapus verifikasi');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showMessage(err.message || 'Gagal menghapus verifikasi', 'bg-red-600 text-white'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function openVerifikasiLapanganEditor(id) {
|
||||||
|
const record = verifikasiLapanganRecords.find(item => Number(item.id) === Number(id));
|
||||||
|
if (!record) {
|
||||||
|
showMessage('Data verifikasi tidak ditemukan', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openVerifikasiLapanganModal(Number(record.latitude), Number(record.longitude), record);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openVerifikasiFromPenduduk(id) {
|
||||||
|
const record = pendudukMiskinRecords.find(item => Number(item.id) === Number(id));
|
||||||
|
if (!record) {
|
||||||
|
showMessage('Data penduduk miskin tidak ditemukan', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openVerifikasiLapanganModal(Number(record.latitude), Number(record.longitude), {
|
||||||
|
jenis_target: 'penduduk_miskin',
|
||||||
|
target_nama: record.nama_ketua_kk || '',
|
||||||
|
petugas_nama: '',
|
||||||
|
status_verifikasi: 'menunggu',
|
||||||
|
hasil_temuan: '',
|
||||||
|
tindak_lanjut: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAduanWargaPopup(record) {
|
||||||
|
const badge = getAduanStatusBadge(record.status_tindak_lanjut);
|
||||||
|
const fotoHtml = record.foto ? `<img src="${record.foto}" alt="Foto aduan" class="mt-2 w-full rounded-lg max-h-48 object-cover">` : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="w-full p-4">
|
||||||
|
<h3 class="text-lg font-bold text-slate-800">Aduan Warga</h3>
|
||||||
|
<p class="mt-1 text-sm text-slate-600">Kategori: ${record.kategori || '-'}</p>
|
||||||
|
<p class="mt-1 text-sm text-slate-600">Pelapor: ${record.pelapor_nama || '-'}</p>
|
||||||
|
<p class="mt-1 text-sm text-slate-600">Kontak: ${record.kontak_pelapor || '-'}</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="registry-chip ${badge.className}">${badge.label}</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-slate-600"><strong>Deskripsi:</strong> ${record.deskripsi || '-'}</p>
|
||||||
|
${record.tindak_lanjut ? `<p class="mt-1 text-sm text-slate-600"><strong>Tindak lanjut:</strong> ${record.tindak_lanjut}</p>` : ''}
|
||||||
|
<p class="mt-1 text-xs text-slate-500">📍 ${Number(record.latitude).toFixed(5)}, ${Number(record.longitude).toFixed(5)}</p>
|
||||||
|
${fotoHtml}
|
||||||
|
<div class="mt-3 grid grid-cols-2 gap-2 border-t pt-3">
|
||||||
|
<button onclick="openAduanWargaEditor(${record.id})" class="rounded-lg bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700 hover:bg-indigo-100">Edit</button>
|
||||||
|
<button onclick="deleteAduanWarga(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAduanWargaModal(lat, lng, record = null) {
|
||||||
|
pendingAduanCoords = { lat, lng };
|
||||||
|
document.getElementById('aduanWargaId').value = record ? String(record.id) : '';
|
||||||
|
document.getElementById('aduanKategori').value = record ? (record.kategori || '') : '';
|
||||||
|
document.getElementById('aduanPelaporNama').value = record ? (record.pelapor_nama || '') : '';
|
||||||
|
document.getElementById('aduanKontakPelapor').value = record ? (record.kontak_pelapor || '') : '';
|
||||||
|
document.getElementById('aduanStatus').value = record ? (record.status_tindak_lanjut || 'baru') : 'baru';
|
||||||
|
document.getElementById('aduanDeskripsi').value = record ? (record.deskripsi || '') : '';
|
||||||
|
document.getElementById('aduanTindakLanjut').value = record ? (record.tindak_lanjut || '') : '';
|
||||||
|
document.getElementById('aduanCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||||
|
document.getElementById('aduanFoto').value = '';
|
||||||
|
|
||||||
|
const modal = document.getElementById('aduanWargaModal');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
suppressAutoReload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAduanWargaModal() {
|
||||||
|
const modal = document.getElementById('aduanWargaModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
pendingAduanCoords = null;
|
||||||
|
suppressAutoReload = !!document.querySelector('[id^="edit-mode-"]:not(.hidden)') || !!editingFeatureId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAduanWarga() {
|
||||||
|
if (!pendingAduanCoords) {
|
||||||
|
showMessage('Koordinat aduan tidak ditemukan', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = document.getElementById('aduanWargaId').value;
|
||||||
|
const kategori = document.getElementById('aduanKategori').value.trim();
|
||||||
|
const pelaporNama = document.getElementById('aduanPelaporNama').value.trim();
|
||||||
|
const deskripsi = document.getElementById('aduanDeskripsi').value.trim();
|
||||||
|
|
||||||
|
if (!kategori || !pelaporNama || !deskripsi) {
|
||||||
|
showMessage('Kategori, nama pelapor, dan deskripsi wajib diisi', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('kategori', kategori);
|
||||||
|
formData.append('pelapor_nama', pelaporNama);
|
||||||
|
formData.append('kontak_pelapor', document.getElementById('aduanKontakPelapor').value.trim());
|
||||||
|
formData.append('deskripsi', deskripsi);
|
||||||
|
formData.append('status_tindak_lanjut', document.getElementById('aduanStatus').value);
|
||||||
|
formData.append('tindak_lanjut', document.getElementById('aduanTindakLanjut').value.trim());
|
||||||
|
formData.append('latitude', pendingAduanCoords.lat);
|
||||||
|
formData.append('longitude', pendingAduanCoords.lng);
|
||||||
|
|
||||||
|
const fileInput = document.getElementById('aduanFoto');
|
||||||
|
if (fileInput.files && fileInput.files[0]) {
|
||||||
|
formData.append('foto', fileInput.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = id ? 'update_aduan_warga.php' : 'save_aduan_warga.php';
|
||||||
|
if (id) {
|
||||||
|
formData.append('id', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(endpoint, { method: 'POST', body: formData })
|
||||||
|
.then(res => parseJsonResponse(res, endpoint))
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showMessage('✅ Aduan warga berhasil disimpan', 'bg-green-600 text-white');
|
||||||
|
closeAduanWargaModal();
|
||||||
|
loadAduanWarga(true);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Gagal menyimpan aduan');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showMessage(err.message || 'Gagal menyimpan aduan', 'bg-red-600 text-white'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAduanWarga(id) {
|
||||||
|
if (!confirm('Hapus aduan warga ini?')) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
|
||||||
|
fetch('delete_aduan_warga.php', { method: 'POST', body: formData })
|
||||||
|
.then(res => parseJsonResponse(res, 'delete_aduan_warga.php'))
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showMessage('🗑️ Aduan warga berhasil dihapus', 'bg-orange-600 text-white');
|
||||||
|
loadAduanWarga(true);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Gagal menghapus aduan');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showMessage(err.message || 'Gagal menghapus aduan', 'bg-red-600 text-white'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAduanWargaEditor(id) {
|
||||||
|
const record = aduanWargaRecords.find(item => Number(item.id) === Number(id));
|
||||||
|
if (!record) {
|
||||||
|
showMessage('Data aduan tidak ditemukan', 'bg-red-600 text-white');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openAduanWargaModal(Number(record.latitude), Number(record.longitude), record);
|
||||||
|
}
|
||||||
|
|
||||||
function saveRumahIbadah() {
|
function saveRumahIbadah() {
|
||||||
if (!pendingRumahIbadahCoords) {
|
if (!pendingRumahIbadahCoords) {
|
||||||
showMessage('Koordinat rumah ibadah tidak ditemukan', 'bg-red-600 text-white');
|
showMessage('Koordinat rumah ibadah tidak ditemukan', 'bg-red-600 text-white');
|
||||||
@@ -2608,16 +3184,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPendudukMiskinIcon() {
|
|
||||||
return L.divIcon({
|
|
||||||
className: 'custom-marker',
|
|
||||||
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="#b91c1c" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="8" r="4"/><path d="M4 22a8 8 0 0 1 16 0"/></svg>`,
|
|
||||||
iconSize: [32, 40],
|
|
||||||
iconAnchor: [16, 40],
|
|
||||||
popupAnchor: [0, -40]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearRumahIbadahLayers() {
|
function clearRumahIbadahLayers() {
|
||||||
rumahIbadahLayer.clearLayers();
|
rumahIbadahLayer.clearLayers();
|
||||||
rumahIbadahLayerMap = {};
|
rumahIbadahLayerMap = {};
|
||||||
@@ -2700,7 +3266,7 @@
|
|||||||
|
|
||||||
data.forEach(record => {
|
data.forEach(record => {
|
||||||
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||||
icon: createPendudukMiskinIcon()
|
icon: createPendudukMiskinIcon(record.bantuan_status)
|
||||||
}).addTo(pendudukMiskinLayer);
|
}).addTo(pendudukMiskinLayer);
|
||||||
|
|
||||||
marker.bindPopup(buildPendudukMiskinPopup(record));
|
marker.bindPopup(buildPendudukMiskinPopup(record));
|
||||||
@@ -2718,6 +3284,86 @@
|
|||||||
.catch(err => console.error('Gagal memuat penduduk miskin:', err));
|
.catch(err => console.error('Gagal memuat penduduk miskin:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVerifikasiLayerPopup(record) {
|
||||||
|
return buildVerifikasiLapanganPopup(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAduanLayerPopup(record) {
|
||||||
|
return buildAduanWargaPopup(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearVerifikasiLapanganLayers() {
|
||||||
|
verifikasiLapanganLayer.clearLayers();
|
||||||
|
verifikasiLapanganLayerMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAduanWargaLayers() {
|
||||||
|
aduanWargaLayer.clearLayers();
|
||||||
|
aduanWargaLayerMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadVerifikasiLapangan(force = false) {
|
||||||
|
if (!force && (document.hidden || suppressAutoReload)) return;
|
||||||
|
|
||||||
|
fetch('get_verifikasi_lapangan.php')
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('HTTP Error: ' + res.status);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new Error('Format data verifikasi tidak valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
verifikasiLapanganRecords = data;
|
||||||
|
clearVerifikasiLapanganLayers();
|
||||||
|
|
||||||
|
data.forEach(record => {
|
||||||
|
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||||
|
icon: getVerifikasiIcon(record.status_verifikasi)
|
||||||
|
}).addTo(verifikasiLapanganLayer);
|
||||||
|
|
||||||
|
marker.bindPopup(getVerifikasiLayerPopup(record));
|
||||||
|
verifikasiLapanganLayerMap[record.id] = marker;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderInputRegistry();
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Gagal memuat verifikasi lapangan:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAduanWarga(force = false) {
|
||||||
|
if (!force && (document.hidden || suppressAutoReload)) return;
|
||||||
|
|
||||||
|
fetch('get_aduan_warga.php')
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('HTTP Error: ' + res.status);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(text => {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new Error('Format data aduan tidak valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
aduanWargaRecords = data;
|
||||||
|
clearAduanWargaLayers();
|
||||||
|
|
||||||
|
data.forEach(record => {
|
||||||
|
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||||
|
icon: getAduanIcon(record.status_tindak_lanjut)
|
||||||
|
}).addTo(aduanWargaLayer);
|
||||||
|
|
||||||
|
marker.bindPopup(getAduanLayerPopup(record));
|
||||||
|
aduanWargaLayerMap[record.id] = marker;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderInputRegistry();
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Gagal memuat aduan warga:', err));
|
||||||
|
}
|
||||||
|
|
||||||
function convertCircleToPolygon(circleLayer, vertices = 48) {
|
function convertCircleToPolygon(circleLayer, vertices = 48) {
|
||||||
const center = circleLayer.getLatLng();
|
const center = circleLayer.getLatLng();
|
||||||
const radius = Number(circleLayer.getRadius() || 0);
|
const radius = Number(circleLayer.getRadius() || 0);
|
||||||
@@ -3396,7 +4042,9 @@
|
|||||||
'Polygon': polygonLayerGroup,
|
'Polygon': polygonLayerGroup,
|
||||||
'Jalan Rusak': roadDamageLayer,
|
'Jalan Rusak': roadDamageLayer,
|
||||||
'Rumah Ibadah': rumahIbadahLayer,
|
'Rumah Ibadah': rumahIbadahLayer,
|
||||||
'Penduduk Miskin': pendudukMiskinLayer
|
'Penduduk Miskin': pendudukMiskinLayer,
|
||||||
|
'Verifikasi Lapangan': verifikasiLapanganLayer,
|
||||||
|
'Aduan Warga': aduanWargaLayer
|
||||||
}, {
|
}, {
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
position: 'topleft'
|
position: 'topleft'
|
||||||
@@ -3415,6 +4063,8 @@
|
|||||||
pendudukMiskinBtn.classList.toggle('active', activeMode === 'pendudukMiskin');
|
pendudukMiskinBtn.classList.toggle('active', activeMode === 'pendudukMiskin');
|
||||||
choroplethBtn.classList.toggle('active', activeMode === 'choropleth');
|
choroplethBtn.classList.toggle('active', activeMode === 'choropleth');
|
||||||
roadDamageBtn.classList.toggle('active', activeMode === 'roadDamage');
|
roadDamageBtn.classList.toggle('active', activeMode === 'roadDamage');
|
||||||
|
verifikasiBtn.classList.toggle('active', activeMode === 'verifikasiLapangan');
|
||||||
|
aduanWargaBtn.classList.toggle('active', activeMode === 'aduanWarga');
|
||||||
}
|
}
|
||||||
|
|
||||||
const registryBtn = L.DomUtil.create('a', '', container);
|
const registryBtn = L.DomUtil.create('a', '', container);
|
||||||
@@ -3459,6 +4109,18 @@
|
|||||||
roadDamageBtn.title = 'Lapor Jalan Rusak';
|
roadDamageBtn.title = 'Lapor Jalan Rusak';
|
||||||
roadDamageBtn.innerHTML = 'Jr';
|
roadDamageBtn.innerHTML = 'Jr';
|
||||||
|
|
||||||
|
const verifikasiBtn = L.DomUtil.create('a', '', container);
|
||||||
|
verifikasiBtn.href = '#';
|
||||||
|
verifikasiBtn.id = 'leafletVerifikasiBtn';
|
||||||
|
verifikasiBtn.title = 'Verifikasi Lapangan';
|
||||||
|
verifikasiBtn.innerHTML = 'Vf';
|
||||||
|
|
||||||
|
const aduanWargaBtn = L.DomUtil.create('a', '', container);
|
||||||
|
aduanWargaBtn.href = '#';
|
||||||
|
aduanWargaBtn.id = 'leafletAduanWargaBtn';
|
||||||
|
aduanWargaBtn.title = 'Aduan Warga';
|
||||||
|
aduanWargaBtn.innerHTML = 'Ad';
|
||||||
|
|
||||||
L.DomEvent.disableClickPropagation(container);
|
L.DomEvent.disableClickPropagation(container);
|
||||||
|
|
||||||
L.DomEvent.on(registryBtn, 'click', function(event) {
|
L.DomEvent.on(registryBtn, 'click', function(event) {
|
||||||
@@ -3508,6 +4170,22 @@
|
|||||||
showMessage('Mode laporan jalan rusak aktif. Klik peta untuk menandai lokasi.', 'bg-red-600 text-white');
|
showMessage('Mode laporan jalan rusak aktif. Klik peta untuk menandai lokasi.', 'bg-red-600 text-white');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
L.DomEvent.on(verifikasiBtn, 'click', function(event) {
|
||||||
|
L.DomEvent.stop(event);
|
||||||
|
setDataMode('verifikasiLapangan');
|
||||||
|
toggleInputRegistry(false);
|
||||||
|
setUtilityModeActive('verifikasiLapangan');
|
||||||
|
showMessage('Mode verifikasi lapangan aktif. Klik peta untuk mencatat hasil cek.', 'bg-emerald-600 text-white');
|
||||||
|
});
|
||||||
|
|
||||||
|
L.DomEvent.on(aduanWargaBtn, 'click', function(event) {
|
||||||
|
L.DomEvent.stop(event);
|
||||||
|
setDataMode('aduanWarga');
|
||||||
|
toggleInputRegistry(false);
|
||||||
|
setUtilityModeActive('aduanWarga');
|
||||||
|
showMessage('Mode aduan warga aktif. Klik peta untuk mencatat laporan.', 'bg-fuchsia-600 text-white');
|
||||||
|
});
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3740,6 +4418,16 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appMode === 'verifikasiLapangan') {
|
||||||
|
openVerifikasiLapanganModal(e.latlng.lat, e.latlng.lng);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appMode === 'aduanWarga') {
|
||||||
|
openAduanWargaModal(e.latlng.lat, e.latlng.lng);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (appMode === 'rumahIbadah') {
|
if (appMode === 'rumahIbadah') {
|
||||||
openRumahIbadahModal(e.latlng.lat, e.latlng.lng);
|
openRumahIbadahModal(e.latlng.lat, e.latlng.lng);
|
||||||
return;
|
return;
|
||||||
@@ -3788,6 +4476,8 @@
|
|||||||
loadFeatures();
|
loadFeatures();
|
||||||
loadRumahIbadah();
|
loadRumahIbadah();
|
||||||
loadPendudukMiskin();
|
loadPendudukMiskin();
|
||||||
|
loadVerifikasiLapangan();
|
||||||
|
loadAduanWarga();
|
||||||
}, REFRESH_INTERVAL_MS);
|
}, REFRESH_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3797,6 +4487,8 @@
|
|||||||
loadFeatures(true);
|
loadFeatures(true);
|
||||||
loadRumahIbadah(true);
|
loadRumahIbadah(true);
|
||||||
loadPendudukMiskin(true);
|
loadPendudukMiskin(true);
|
||||||
|
loadVerifikasiLapangan(true);
|
||||||
|
loadAduanWarga(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3841,6 +4533,8 @@
|
|||||||
loadRumahIbadah(true);
|
loadRumahIbadah(true);
|
||||||
loadPendudukMiskin(true);
|
loadPendudukMiskin(true);
|
||||||
loadRoadDamage(true);
|
loadRoadDamage(true);
|
||||||
|
loadVerifikasiLapangan(true);
|
||||||
|
loadAduanWarga(true);
|
||||||
loadExternalRoadNetwork(true);
|
loadExternalRoadNetwork(true);
|
||||||
startAutoRefresh();
|
startAutoRefresh();
|
||||||
};
|
};
|
||||||
@@ -3859,6 +4553,14 @@
|
|||||||
document.getElementById('modePendudukMiskinBtn').addEventListener('click', () => setDataMode('pendudukMiskin'));
|
document.getElementById('modePendudukMiskinBtn').addEventListener('click', () => setDataMode('pendudukMiskin'));
|
||||||
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
|
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
|
||||||
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
|
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
|
||||||
|
const modeVerifikasiBtn = document.getElementById('modeVerifikasiBtn');
|
||||||
|
if (modeVerifikasiBtn) {
|
||||||
|
modeVerifikasiBtn.addEventListener('click', () => setDataMode('verifikasiLapangan'));
|
||||||
|
}
|
||||||
|
const modeAduanWargaBtn = document.getElementById('modeAduanWargaBtn');
|
||||||
|
if (modeAduanWargaBtn) {
|
||||||
|
modeAduanWargaBtn.addEventListener('click', () => setDataMode('aduanWarga'));
|
||||||
|
}
|
||||||
const miniModePointBtn = document.getElementById('miniModePointBtn');
|
const miniModePointBtn = document.getElementById('miniModePointBtn');
|
||||||
if (miniModePointBtn) {
|
if (miniModePointBtn) {
|
||||||
miniModePointBtn.addEventListener('click', () => {
|
miniModePointBtn.addEventListener('click', () => {
|
||||||
@@ -3894,6 +4596,20 @@
|
|||||||
showMessage('Klik peta untuk menambah penduduk miskin', 'bg-indigo-600 text-white');
|
showMessage('Klik peta untuk menambah penduduk miskin', 'bg-indigo-600 text-white');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const miniModeVerifikasiBtn = document.getElementById('miniModeVerifikasiBtn');
|
||||||
|
if (miniModeVerifikasiBtn) {
|
||||||
|
miniModeVerifikasiBtn.addEventListener('click', () => {
|
||||||
|
setDataMode('verifikasiLapangan');
|
||||||
|
showMessage('Klik peta untuk verifikasi lapangan', 'bg-emerald-600 text-white');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const miniModeAduanWargaBtn = document.getElementById('miniModeAduanWargaBtn');
|
||||||
|
if (miniModeAduanWargaBtn) {
|
||||||
|
miniModeAduanWargaBtn.addEventListener('click', () => {
|
||||||
|
setDataMode('aduanWarga');
|
||||||
|
showMessage('Klik peta untuk membuat aduan warga', 'bg-fuchsia-600 text-white');
|
||||||
|
});
|
||||||
|
}
|
||||||
const toggleRegistryDrawerBtn = document.getElementById('toggleRegistryDrawerBtn');
|
const toggleRegistryDrawerBtn = document.getElementById('toggleRegistryDrawerBtn');
|
||||||
if (toggleRegistryDrawerBtn) {
|
if (toggleRegistryDrawerBtn) {
|
||||||
toggleRegistryDrawerBtn.addEventListener('click', () => toggleInputRegistry());
|
toggleRegistryDrawerBtn.addEventListener('click', () => toggleInputRegistry());
|
||||||
@@ -4033,6 +4749,20 @@
|
|||||||
if (event.target.id === 'pendudukMiskinModal') closePendudukMiskinModal();
|
if (event.target.id === 'pendudukMiskinModal') closePendudukMiskinModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('saveVerifikasiLapanganBtn').addEventListener('click', saveVerifikasiLapangan);
|
||||||
|
document.getElementById('cancelVerifikasiLapanganBtn').addEventListener('click', closeVerifikasiLapanganModal);
|
||||||
|
document.getElementById('closeVerifikasiLapanganModalBtn').addEventListener('click', closeVerifikasiLapanganModal);
|
||||||
|
document.getElementById('verifikasiLapanganModal').addEventListener('click', (event) => {
|
||||||
|
if (event.target.id === 'verifikasiLapanganModal') closeVerifikasiLapanganModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('saveAduanWargaBtn').addEventListener('click', saveAduanWarga);
|
||||||
|
document.getElementById('cancelAduanWargaBtn').addEventListener('click', closeAduanWargaModal);
|
||||||
|
document.getElementById('closeAduanWargaModalBtn').addEventListener('click', closeAduanWargaModal);
|
||||||
|
document.getElementById('aduanWargaModal').addEventListener('click', (event) => {
|
||||||
|
if (event.target.id === 'aduanWargaModal') closeAduanWargaModal();
|
||||||
|
});
|
||||||
|
|
||||||
// Road Damage Modal Event Listeners
|
// Road Damage Modal Event Listeners
|
||||||
document.getElementById('saveRoadDamageBtn').addEventListener('click', saveRoadDamage);
|
document.getElementById('saveRoadDamageBtn').addEventListener('click', saveRoadDamage);
|
||||||
document.getElementById('cancelRoadDamageBtn').addEventListener('click', closeRoadDamageModal);
|
document.getElementById('cancelRoadDamageBtn').addEventListener('click', closeRoadDamageModal);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success'=>true,'msg'=>'API placeholder untuk layer groups']);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID fitur tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("DELETE FROM spatial_features WHERE id = ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Fitur berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, nama_objek, kategori, status_objek, type, geometry_data, nilai_ukur, created_at
|
||||||
|
FROM spatial_features
|
||||||
|
ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$features = [];
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
$row['geometry_data'] = json_decode($row['geometry_data']);
|
||||||
|
$row['nilai_ukur'] = (float)$row['nilai_ukur'];
|
||||||
|
$features[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($features);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Layer Groups & Choropleth</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<style>html,body,#map{height:100%;margin:0}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
const map = L.map('map').setView([-0.03,109.34],11);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||||
|
|
||||||
|
// Layer group for SPBU points
|
||||||
|
const spbuLayer = L.layerGroup();
|
||||||
|
const spbuMarkers = [L.marker([-0.03,109.34]).bindPopup('SPBU A'), L.marker([-0.04,109.36]).bindPopup('SPBU B')];
|
||||||
|
spbuMarkers.forEach(m=>spbuLayer.addLayer(m));
|
||||||
|
|
||||||
|
// Choropleth layer (load from ../data/pontianak-kecamatan.geojson if available)
|
||||||
|
let choropleth = L.layerGroup();
|
||||||
|
fetch('../data/pontianak-kecamatan.geojson').then(r=>r.json()).then(j=>{
|
||||||
|
function getColor(d){return d>10000?'#800026':d>5000?'#BD0026':d>2000?'#E31A1C':d>1000?'#FC4E2A':'#FD8D3C';}
|
||||||
|
choropleth = L.geoJSON(j,{style: f=>({fillColor:getColor(f.properties.luas||0),weight:1,fillOpacity:0.7,color:'#333'})}).addTo(map);
|
||||||
|
controlLayers.addOverlay(choropleth,'Choropleth');
|
||||||
|
}).catch(()=>{ /* ignore missing file */ });
|
||||||
|
|
||||||
|
const base = {};
|
||||||
|
const overlays = {'SPBU Points': spbuLayer};
|
||||||
|
const controlLayers = L.control.layers(base, overlays, {collapsed:false}).addTo(map);
|
||||||
|
spbuLayer.addTo(map);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
ini_set('html_errors', '0');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$kategori = trim($_POST['kategori'] ?? '');
|
||||||
|
if ($kategori === 'Parsil Tanah') {
|
||||||
|
$kategori = 'Tanah';
|
||||||
|
}
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
$type = trim($_POST['type'] ?? '');
|
||||||
|
$geometry = $_POST['geometry'] ?? '';
|
||||||
|
$nilai = (float)($_POST['nilai'] ?? 0);
|
||||||
|
|
||||||
|
if ($nama === '' || $kategori === '' || $status === '' || $type === '' || $geometry === '') {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data fitur tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO spatial_features (nama_objek, kategori, status_objek, type, geometry_data, nilai_ukur) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("sssssd", $nama, $kategori, $status, $type, $geometry, $nilai);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data berhasil disimpan']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$kategori = trim($_POST['kategori'] ?? '');
|
||||||
|
if ($kategori === 'Parsil Tanah') {
|
||||||
|
$kategori = 'Tanah';
|
||||||
|
}
|
||||||
|
$status = trim($_POST['status'] ?? '');
|
||||||
|
$type = trim($_POST['type'] ?? '');
|
||||||
|
$geometry = $_POST['geometry'] ?? '';
|
||||||
|
$nilai = (float)($_POST['nilai'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0 || $nama === '' || $kategori === '' || $status === '' || $type === '' || $geometry === '') {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data fitur tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE spatial_features SET nama_objek = ?, kategori = ?, status_objek = ?, type = ?, geometry_data = ?, nilai_ukur = ? WHERE id = ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bind_param("sssssdi", $nama, $kategori, $status, $type, $geometry, $nilai, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Fitur berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Rancangan Sistem Geospasial untuk Kontribusi Pengentasan Kemiskinan
|
||||||
|
|
||||||
|
Ringkasan singkat: rancangan ini menggabungkan data titik (SPBU, fasilitas), jaringan jalan (polyline), parsel tanah (polygon dengan luas otomatis), dan peta tematik (choropleth). Tujuan: menyediakan basis data spasial untuk analisis kebutuhan bantuan, akses layanan, dan prioritas intervensi.
|
||||||
|
|
||||||
|
Komponen:
|
||||||
|
- Data: sampel sensus, kemiskinan, infrastruktur, peta batas administrasi, penggunaan lahan, parsel tanah.
|
||||||
|
- Backend: API PHP ringan untuk CRUD GeoJSON + penyimpanan file atau database spatial (PostGIS direkomendasikan).
|
||||||
|
- Frontend: Leaflet + plugins (Draw, Turf.js) untuk editing, pengukuran, dan visualisasi layer groups.
|
||||||
|
- Analitik: agregasi statistik per kelurahan/kecamatan untuk choropleth (tingkat kemiskinan, akses fasilitas).
|
||||||
|
|
||||||
|
Alur kerja singkat:
|
||||||
|
1. Kumpulkan & validasi data atribut (nama, id, status ekonomis, luas parsel).
|
||||||
|
2. Masukkan ke sistem: titik/line/polygon.
|
||||||
|
3. Hitung atribut turunan: luas parsel (m²), distance-to-nearest-facility, road-condition index.
|
||||||
|
4. Hasilkan peta tematik & laporan untuk prioritisasi intervensi.
|
||||||
|
|
||||||
|
Rekomendasi teknis singkat:
|
||||||
|
- Gunakan PostGIS untuk query spasial yang efisien.
|
||||||
|
- Simpan snapshot GeoJSON untuk interoperabilitas.
|
||||||
|
- Bangun API otentikasi dan logging perubahan (audit trail) untuk kebijakan bantuan.
|
||||||
|
|
||||||
|
Catatan: folder lain di repo ini berisi contoh starter untuk tiap fitur (uji_spbu, fitur_jalan_parsel, layer_groups_choropleth).
|
||||||
|
|
||||||
|
Global Leaflet loader:
|
||||||
|
- File: `global/leaflet-loader.js` — tambahkan tag berikut ke halaman HTML Anda untuk memuat Leaflet dari satu sumber global:
|
||||||
|
|
||||||
|
<script src="../global/leaflet-loader.js"></script>
|
||||||
|
|
||||||
|
Gunakan `rancangan_sistem/index.html` sebagai contoh integrasi: halaman akan memanggil `../get_rumah_ibadah.php` dan `../get_penduduk_miskin.php` dari root repository dan menambahkan data ke peta.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("DELETE FROM penduduk_miskin WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("DELETE FROM rumah_ibadah WHERE id = ?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m, bantuan_status, bantuan_catatan, bantuan_updated_at, created_at
|
||||||
|
FROM penduduk_miskin
|
||||||
|
ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$row['latitude'] = (float)$row['latitude'];
|
||||||
|
$row['longitude'] = (float)$row['longitude'];
|
||||||
|
$row['jumlah_anggota'] = (int)$row['jumlah_anggota'];
|
||||||
|
$row['rumah_ibadah_id'] = $row['rumah_ibadah_id'] !== null ? (int)$row['rumah_ibadah_id'] : null;
|
||||||
|
$row['jarak_ke_rumah_ibadah_m'] = $row['jarak_ke_rumah_ibadah_m'] !== null ? (float)$row['jarak_ke_rumah_ibadah_m'] : null;
|
||||||
|
$row['bantuan_status'] = $row['bantuan_status'] ?: 'belum';
|
||||||
|
$row['bantuan_catatan'] = $row['bantuan_catatan'] ?? '';
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m, bantuan_status, bantuan_catatan, bantuan_updated_at, created_at FROM penduduk_miskin ORDER BY created_at DESC";
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$features = [];
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$lat = isset($row['latitude']) ? (float)$row['latitude'] : null;
|
||||||
|
$lng = isset($row['longitude']) ? (float)$row['longitude'] : null;
|
||||||
|
$props = $row;
|
||||||
|
unset($props['latitude'], $props['longitude']);
|
||||||
|
$features[] = [
|
||||||
|
'type' => 'Feature',
|
||||||
|
'geometry' => [ 'type' => 'Point', 'coordinates' => [$lng, $lat] ],
|
||||||
|
'properties' => $props
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([ 'type' => 'FeatureCollection', 'features' => $features ]);
|
||||||
|
$conn->close();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, nama, alamat, pic, latitude, longitude, radius_m, created_at
|
||||||
|
FROM rumah_ibadah
|
||||||
|
ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$row['latitude'] = (float)$row['latitude'];
|
||||||
|
$row['longitude'] = (float)$row['longitude'];
|
||||||
|
$row['radius_m'] = (int)$row['radius_m'];
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
$sql = "SELECT id, nama, alamat, pic, latitude, longitude, radius_m, created_at FROM rumah_ibadah ORDER BY created_at DESC";
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$features = [];
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$lat = isset($row['latitude']) ? (float)$row['latitude'] : null;
|
||||||
|
$lng = isset($row['longitude']) ? (float)$row['longitude'] : null;
|
||||||
|
$props = $row;
|
||||||
|
unset($props['latitude'], $props['longitude']);
|
||||||
|
$features[] = [
|
||||||
|
'type' => 'Feature',
|
||||||
|
'geometry' => [ 'type' => 'Point', 'coordinates' => [$lng, $lat] ],
|
||||||
|
'properties' => $props
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([ 'type' => 'FeatureCollection', 'features' => $features ]);
|
||||||
|
$conn->close();
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Rancangan Sistem — Integrasi Rumah Ibadah & Penduduk Miskin</title>
|
||||||
|
<style>html,body,#map{height:100%;margin:0} .info{position:absolute;right:8px;top:8px;z-index:400;background:#fff;padding:6px}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="info">Data: Rumah Ibadah & Penduduk Miskin (dari API)</div>
|
||||||
|
|
||||||
|
<script src="../global/leaflet-loader.js"></script>
|
||||||
|
<script>
|
||||||
|
(function waitForLeaflet(){
|
||||||
|
if(window.L) return init();
|
||||||
|
window.addEventListener('leaflet:loaded', init);
|
||||||
|
setTimeout(function(){ if(window.L) init(); }, 1000);
|
||||||
|
function init(){
|
||||||
|
const map = L.map('map').setView([-0.03,109.34],12);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:''}).addTo(map);
|
||||||
|
|
||||||
|
const overlays = {};
|
||||||
|
const control = L.control.layers(null, overlays, {collapsed:false}).addTo(map);
|
||||||
|
|
||||||
|
function addGeoData(data, title){
|
||||||
|
try{
|
||||||
|
if(!data) return;
|
||||||
|
if(data.type==='FeatureCollection' || data.features){
|
||||||
|
const layer = L.geoJSON(data, {onEachFeature:function(feature,layer){
|
||||||
|
const p = feature.properties || {};
|
||||||
|
layer.bindPopup(title+'<br>'+Object.keys(p).map(k=>k+': '+p[k]).join('<br>'));
|
||||||
|
}}).addTo(map);
|
||||||
|
overlays[title]=layer; control.addOverlay(layer,title);
|
||||||
|
} else if(Array.isArray(data)){
|
||||||
|
const lg = L.layerGroup();
|
||||||
|
data.forEach(it=>{
|
||||||
|
const lat = it.lat || it.latitude || it.y;
|
||||||
|
const lng = it.lng || it.lon || it.longitude || it.x;
|
||||||
|
if(lat && lng){
|
||||||
|
const m = L.marker([lat,lng]).bindPopup(title+'<br>'+(it.nama||it.name||''));
|
||||||
|
lg.addLayer(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lg.addTo(map); overlays[title]=lg; control.addOverlay(lg,title);
|
||||||
|
}
|
||||||
|
}catch(e){ console.error('addGeoData error',e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// load rumah ibadah and penduduk miskin as GeoJSON (endpoints in this folder)
|
||||||
|
fetch('get_rumah_ibadah_geojson.php').then(r=>r.json()).then(j=>addGeoData(j,'Rumah Ibadah')).catch(e=>console.error(e));
|
||||||
|
fetch('get_penduduk_miskin_geojson.php').then(r=>r.json()).then(j=>addGeoData(j,'Penduduk Miskin')).catch(e=>console.error(e));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
|
||||||
|
$earthRadius = 6371000;
|
||||||
|
$toRadians = function ($value) {
|
||||||
|
return $value * M_PI / 180;
|
||||||
|
};
|
||||||
|
|
||||||
|
$deltaLat = $toRadians($lat2 - $lat1);
|
||||||
|
$deltaLng = $toRadians($lng2 - $lng1);
|
||||||
|
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
|
||||||
|
|
||||||
|
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestRumahIbadah($conn, $lat, $lng) {
|
||||||
|
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
|
||||||
|
if (!$result || $result->num_rows === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nearest = null;
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
|
||||||
|
if ($nearest === null || $distance < $nearest['distance']) {
|
||||||
|
$nearest = [
|
||||||
|
'id' => (int)$row['id'],
|
||||||
|
'nama' => $row['nama'],
|
||||||
|
'distance' => $distance
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
|
||||||
|
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
|
||||||
|
$lat = $_POST['latitude'] ?? null;
|
||||||
|
$lng = $_POST['longitude'] ?? null;
|
||||||
|
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
|
||||||
|
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
|
||||||
|
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
|
||||||
|
|
||||||
|
if ($namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lat = (float)$lat;
|
||||||
|
$lng = (float)$lng;
|
||||||
|
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
|
||||||
|
|
||||||
|
if (!$nearest) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$jarak = $nearest['distance'];
|
||||||
|
$rumahIbadahId = $nearest['id'];
|
||||||
|
$rumahIbadahNama = $nearest['nama'];
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO penduduk_miskin (nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("siddisd", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Penduduk miskin berhasil disimpan']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$alamat = trim($_POST['alamat'] ?? '');
|
||||||
|
$pic = trim($_POST['pic'] ?? '');
|
||||||
|
$lat = $_POST['latitude'] ?? null;
|
||||||
|
$lng = $_POST['longitude'] ?? null;
|
||||||
|
$radius = $_POST['radius_m'] ?? 1000;
|
||||||
|
|
||||||
|
if ($nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lat = (float)$lat;
|
||||||
|
$lng = (float)$lng;
|
||||||
|
$radius = max(50, (int)$radius);
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO rumah_ibadah (nama, alamat, pic, latitude, longitude, radius_m) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("sssddi", $nama, $alamat, $pic, $lat, $lng, $radius);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil disimpan']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Metode tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$status = strtolower(trim($_POST['bantuan_status'] ?? 'belum'));
|
||||||
|
$catatan = trim($_POST['bantuan_catatan'] ?? '');
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($status, ['belum', 'proses', 'sudah'], true)) {
|
||||||
|
$status = 'belum';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE penduduk_miskin SET bantuan_status = ?, bantuan_catatan = ?, bantuan_updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||||
|
$stmt->bind_param("ssi", $status, $catatan, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$updatedAtResult = $conn->query("SELECT bantuan_updated_at FROM penduduk_miskin WHERE id = " . (int)$id . " LIMIT 1");
|
||||||
|
$updatedAt = null;
|
||||||
|
if ($updatedAtResult && $updatedAtResult->num_rows > 0) {
|
||||||
|
$row = $updatedAtResult->fetch_assoc();
|
||||||
|
$updatedAt = $row['bantuan_updated_at'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Status bantuan berhasil diperbarui',
|
||||||
|
'bantuan_updated_at' => $updatedAt
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
|
||||||
|
$earthRadius = 6371000;
|
||||||
|
$toRadians = function ($value) {
|
||||||
|
return $value * M_PI / 180;
|
||||||
|
};
|
||||||
|
|
||||||
|
$deltaLat = $toRadians($lat2 - $lat1);
|
||||||
|
$deltaLng = $toRadians($lng2 - $lng1);
|
||||||
|
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
|
||||||
|
|
||||||
|
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNearestRumahIbadah($conn, $lat, $lng) {
|
||||||
|
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
|
||||||
|
if (!$result || $result->num_rows === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nearest = null;
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
|
||||||
|
if ($nearest === null || $distance < $nearest['distance']) {
|
||||||
|
$nearest = [
|
||||||
|
'id' => (int)$row['id'],
|
||||||
|
'nama' => $row['nama'],
|
||||||
|
'distance' => $distance
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
|
||||||
|
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
|
||||||
|
$lat = $_POST['latitude'] ?? null;
|
||||||
|
$lng = $_POST['longitude'] ?? null;
|
||||||
|
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
|
||||||
|
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
|
||||||
|
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
|
||||||
|
|
||||||
|
if ($id <= 0 || $namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lat = (float)$lat;
|
||||||
|
$lng = (float)$lng;
|
||||||
|
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
|
||||||
|
|
||||||
|
if (!$nearest) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$jarak = $nearest['distance'];
|
||||||
|
$rumahIbadahId = $nearest['id'];
|
||||||
|
$rumahIbadahNama = $nearest['nama'];
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_ketua_kk = ?, jumlah_anggota = ?, latitude = ?, longitude = ?, rumah_ibadah_id = ?, rumah_ibadah_nama = ?, jarak_ke_rumah_ibadah_m = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("siddisdi", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include __DIR__ . '/../db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$nama = trim($_POST['nama'] ?? '');
|
||||||
|
$alamat = trim($_POST['alamat'] ?? '');
|
||||||
|
$pic = trim($_POST['pic'] ?? '');
|
||||||
|
$lat = $_POST['latitude'] ?? null;
|
||||||
|
$lng = $_POST['longitude'] ?? null;
|
||||||
|
$radius = $_POST['radius_m'] ?? 1000;
|
||||||
|
|
||||||
|
if ($id <= 0 || $nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lat = (float)$lat;
|
||||||
|
$lng = (float)$lng;
|
||||||
|
$radius = max(50, (int)$radius);
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("UPDATE rumah_ibadah SET nama = ?, alamat = ?, pic = ?, latitude = ?, longitude = ?, radius_m = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param("sssddii", $nama, $alamat, $pic, $lat, $lng, $radius, $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$kategori = trim($_POST['kategori'] ?? '');
|
||||||
|
$pelaporNama = trim($_POST['pelapor_nama'] ?? '');
|
||||||
|
$kontakPelapor = trim($_POST['kontak_pelapor'] ?? '');
|
||||||
|
$deskripsi = trim($_POST['deskripsi'] ?? '');
|
||||||
|
$statusTindakLanjut = trim($_POST['status_tindak_lanjut'] ?? 'baru');
|
||||||
|
$tindakLanjut = trim($_POST['tindak_lanjut'] ?? '');
|
||||||
|
$latitude = $_POST['latitude'] ?? '';
|
||||||
|
$longitude = $_POST['longitude'] ?? '';
|
||||||
|
|
||||||
|
if ($kategori === '' || $pelaporNama === '' || $deskripsi === '' || empty($latitude) || empty($longitude)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Kategori, nama pelapor, deskripsi, dan koordinat wajib diisi']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = null;
|
||||||
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
$filename = $_FILES['foto']['name'];
|
||||||
|
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed, true)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Format foto hanya JPG, PNG, GIF, WEBP']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir('uploads')) {
|
||||||
|
mkdir('uploads', 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = 'uploads/' . time() . '_' . basename($filename);
|
||||||
|
if (!move_uploaded_file($_FILES['foto']['tmp_name'], $foto)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal upload foto']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare('INSERT INTO aduan_warga (kategori, pelapor_nama, kontak_pelapor, deskripsi, status_tindak_lanjut, tindak_lanjut, foto, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latitude = (float)$latitude;
|
||||||
|
$longitude = (float)$longitude;
|
||||||
|
$stmt->bind_param('sssssssdd', $kategori, $pelaporNama, $kontakPelapor, $deskripsi, $statusTindakLanjut, $tindakLanjut, $foto, $latitude, $longitude);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Aduan warga berhasil disimpan', 'id' => $stmt->insert_id]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$jenisTarget = trim($_POST['jenis_target'] ?? 'penduduk_miskin');
|
||||||
|
$targetNama = trim($_POST['target_nama'] ?? '');
|
||||||
|
$petugasNama = trim($_POST['petugas_nama'] ?? '');
|
||||||
|
$statusVerifikasi = trim($_POST['status_verifikasi'] ?? 'menunggu');
|
||||||
|
$hasilTemuan = trim($_POST['hasil_temuan'] ?? '');
|
||||||
|
$tindakLanjut = trim($_POST['tindak_lanjut'] ?? '');
|
||||||
|
$latitude = $_POST['latitude'] ?? '';
|
||||||
|
$longitude = $_POST['longitude'] ?? '';
|
||||||
|
|
||||||
|
if ($petugasNama === '' || empty($latitude) || empty($longitude)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Nama petugas dan koordinat wajib diisi']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = null;
|
||||||
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
$filename = $_FILES['foto']['name'];
|
||||||
|
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed, true)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Format foto hanya JPG, PNG, GIF, WEBP']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir('uploads')) {
|
||||||
|
mkdir('uploads', 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = 'uploads/' . time() . '_' . basename($filename);
|
||||||
|
if (!move_uploaded_file($_FILES['foto']['tmp_name'], $foto)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal upload foto']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare('INSERT INTO verifikasi_lapangan (jenis_target, target_nama, petugas_nama, status_verifikasi, hasil_temuan, tindak_lanjut, foto, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latitude = (float)$latitude;
|
||||||
|
$longitude = (float)$longitude;
|
||||||
|
$stmt->bind_param('sssssssdd', $jenisTarget, $targetNama, $petugasNama, $statusVerifikasi, $hasilTemuan, $tindakLanjut, $foto, $latitude, $longitude);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Verifikasi lapangan berhasil disimpan', 'id' => $stmt->insert_id]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if(!$input) { echo json_encode(['success'=>false,'msg'=>'no input']); exit; }
|
||||||
|
if($input['action']==='update_status'){
|
||||||
|
// contoh sederhana: simpan ke file log (uji saja)
|
||||||
|
$log = ['time'=>date('c'),'id'=>$input['id'],'status'=>$input['status']];
|
||||||
|
file_put_contents(__DIR__.'/spbu_status.log',json_encode($log)."\n",FILE_APPEND);
|
||||||
|
echo json_encode(['success'=>true,'log'=>$log]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
echo json_encode(['success'=>false,'msg'=>'unknown action']);
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Uji CRUD SPBU — Open/Close</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<style>html,body,#map{height:100%;margin:0}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
const map = L.map('map').setView([-0.03, 109.34], 12);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:''}).addTo(map);
|
||||||
|
|
||||||
|
// contoh data SPBU (id, nama, coords, status)
|
||||||
|
const spbu = [
|
||||||
|
{id:1,name:'SPBU 1',lat:-0.03,lng:109.34,status:'open'},
|
||||||
|
{id:2,name:'SPBU 2',lat:-0.04,lng:109.36,status:'closed'}
|
||||||
|
];
|
||||||
|
|
||||||
|
function iconFor(status){
|
||||||
|
return L.divIcon({className:'spbu-icon',html:`<div style="background:${status==='open'?'green':'red'};width:14px;height:14px;border-radius:7px;border:2px solid #fff"></div>`});
|
||||||
|
}
|
||||||
|
|
||||||
|
spbu.forEach(s=>{
|
||||||
|
const m = L.marker([s.lat,s.lng],{icon:iconFor(s.status)}).addTo(map)
|
||||||
|
.bindPopup(`<b>${s.name}</b><br>Status: <span id=st-${s.id}>${s.status}</span><br><button onclick="toggle(${s.id})">Toggle</button>`);
|
||||||
|
window['marker_'+s.id]=m;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.toggle = async function(id){
|
||||||
|
const s = spbu.find(x=>x.id===id);
|
||||||
|
s.status = s.status==='open'?'closed':'open';
|
||||||
|
await fetch('api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'update_status',id, status:s.status})});
|
||||||
|
const m = window['marker_'+id];
|
||||||
|
m.setIcon(iconFor(s.status));
|
||||||
|
const el = document.getElementById('st-'+id);
|
||||||
|
if(el) el.textContent = s.status;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$kategori = trim($_POST['kategori'] ?? '');
|
||||||
|
$pelaporNama = trim($_POST['pelapor_nama'] ?? '');
|
||||||
|
$kontakPelapor = trim($_POST['kontak_pelapor'] ?? '');
|
||||||
|
$deskripsi = trim($_POST['deskripsi'] ?? '');
|
||||||
|
$statusTindakLanjut = trim($_POST['status_tindak_lanjut'] ?? 'baru');
|
||||||
|
$tindakLanjut = trim($_POST['tindak_lanjut'] ?? '');
|
||||||
|
$latitude = $_POST['latitude'] ?? '';
|
||||||
|
$longitude = $_POST['longitude'] ?? '';
|
||||||
|
|
||||||
|
if ($id <= 0 || $kategori === '' || $pelaporNama === '' || $deskripsi === '' || empty($latitude) || empty($longitude)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data aduan tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fotoSql = '';
|
||||||
|
$foto = null;
|
||||||
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
$filename = $_FILES['foto']['name'];
|
||||||
|
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed, true)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Format foto hanya JPG, PNG, GIF, WEBP']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir('uploads')) {
|
||||||
|
mkdir('uploads', 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = 'uploads/' . time() . '_' . basename($filename);
|
||||||
|
if (!move_uploaded_file($_FILES['foto']['tmp_name'], $foto)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal upload foto']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fotoSql = ', foto = ?';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'UPDATE aduan_warga SET kategori = ?, pelapor_nama = ?, kontak_pelapor = ?, deskripsi = ?, status_tindak_lanjut = ?, tindak_lanjut = ?, latitude = ?, longitude = ?' . $fotoSql . ' WHERE id = ?';
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latitude = (float)$latitude;
|
||||||
|
$longitude = (float)$longitude;
|
||||||
|
if ($foto !== null) {
|
||||||
|
$stmt->bind_param('sssssssddi', $kategori, $pelaporNama, $kontakPelapor, $deskripsi, $statusTindakLanjut, $tindakLanjut, $foto, $latitude, $longitude, $id);
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param('ssssssddi', $kategori, $pelaporNama, $kontakPelapor, $deskripsi, $statusTindakLanjut, $tindakLanjut, $latitude, $longitude, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Aduan warga berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
include 'db_config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$jenisTarget = trim($_POST['jenis_target'] ?? 'penduduk_miskin');
|
||||||
|
$targetNama = trim($_POST['target_nama'] ?? '');
|
||||||
|
$petugasNama = trim($_POST['petugas_nama'] ?? '');
|
||||||
|
$statusVerifikasi = trim($_POST['status_verifikasi'] ?? 'menunggu');
|
||||||
|
$hasilTemuan = trim($_POST['hasil_temuan'] ?? '');
|
||||||
|
$tindakLanjut = trim($_POST['tindak_lanjut'] ?? '');
|
||||||
|
$latitude = $_POST['latitude'] ?? '';
|
||||||
|
$longitude = $_POST['longitude'] ?? '';
|
||||||
|
|
||||||
|
if ($id <= 0 || $petugasNama === '' || empty($latitude) || empty($longitude)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data verifikasi tidak lengkap']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fotoSql = '';
|
||||||
|
$foto = null;
|
||||||
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
$filename = $_FILES['foto']['name'];
|
||||||
|
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed, true)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Format foto hanya JPG, PNG, GIF, WEBP']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir('uploads')) {
|
||||||
|
mkdir('uploads', 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto = 'uploads/' . time() . '_' . basename($filename);
|
||||||
|
if (!move_uploaded_file($_FILES['foto']['tmp_name'], $foto)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal upload foto']);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fotoSql = ', foto = ?';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'UPDATE verifikasi_lapangan SET jenis_target = ?, target_nama = ?, petugas_nama = ?, status_verifikasi = ?, hasil_temuan = ?, tindak_lanjut = ?, latitude = ?, longitude = ?' . $fotoSql . ' WHERE id = ?';
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
if (!$stmt) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latitude = (float)$latitude;
|
||||||
|
$longitude = (float)$longitude;
|
||||||
|
if ($foto !== null) {
|
||||||
|
$stmt->bind_param('sssssssddi', $jenisTarget, $targetNama, $petugasNama, $statusVerifikasi, $hasilTemuan, $tindakLanjut, $foto, $latitude, $longitude, $id);
|
||||||
|
} else {
|
||||||
|
$stmt->bind_param('ssssssddi', $jenisTarget, $targetNama, $petugasNama, $statusVerifikasi, $hasilTemuan, $tindakLanjut, $latitude, $longitude, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Verifikasi lapangan berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user