Files
2026-06-11 21:14:16 +07:00

568 lines
17 KiB
JavaScript

window.appState = {
mode: "view",
activeType: "spbu",
drawControl: null,
layers: {},
layerControl: null,
layerVisibility: {
spbu24: true,
spbuTidak: true,
kemiskinan: true,
masjid: true,
jalan: true,
kavling: true
}
};
window.escapeHtml = function (value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
window.createPointIcon = function (color) {
return L.divIcon({
className: "custom-point-icon",
html: '<span class="point-dot" style="background:' + color + ';"></span>',
iconSize: [18, 18],
iconAnchor: [9, 9],
popupAnchor: [0, -12]
});
};
window.greenIcon = window.createPointIcon("#28a745");
window.redIcon = window.createPointIcon("#dc3545");
window.orangeIcon = window.createPointIcon("#ff8c00");
window.blueIcon = window.createPointIcon("#007bff");
window.calculateLineLength = function (latlngs) {
var total = 0;
for (var i = 1; i < latlngs.length; i += 1) {
total += map.distance(latlngs[i - 1], latlngs[i]);
}
return total;
};
window.calculatePolygonArea = function (latlngs) {
if (L.GeometryUtil && typeof L.GeometryUtil.geodesicArea === "function") {
return L.GeometryUtil.geodesicArea(latlngs);
}
if (!latlngs.length) {
return 0;
}
var earthRadius = 6378137;
var avgLat = latlngs.reduce(function (sum, point) {
return sum + point.lat;
}, 0) / latlngs.length;
var avgLatRad = avgLat * Math.PI / 180;
var projected = latlngs.map(function (point) {
return {
x: earthRadius * point.lng * Math.PI / 180 * Math.cos(avgLatRad),
y: earthRadius * point.lat * Math.PI / 180
};
});
var area = 0;
for (var i = 0; i < projected.length; i += 1) {
var next = projected[(i + 1) % projected.length];
area += (projected[i].x * next.y) - (next.x * projected[i].y);
}
return Math.abs(area / 2);
};
window.formatMeters = function (value) {
return Number(value).toLocaleString("id-ID", {
maximumFractionDigits: 2
}) + " m";
};
window.formatSquareMeters = function (value) {
return Number(value).toLocaleString("id-ID", {
maximumFractionDigits: 2
}) + " m²";
};
window.postForm = async function (url, payload) {
var response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams(payload)
});
var text = (await response.text()).trim();
if (!response.ok || text !== "success") {
throw new Error(text || "Permintaan gagal.");
}
};
window.collectSpbuData = async function () {
return window.openEditModal({
title: "Tambah SPBU",
subtitle: "Lengkapi atribut SPBU untuk titik yang dipilih di peta.",
fields: [
{ name: "nama", label: "Nama SPBU", value: "", required: true },
{ name: "no", label: "Nomor SPBU", value: "", required: true },
{
name: "status",
label: "Status Operasional",
type: "select",
value: "24jam",
options: [
{ value: "24jam", label: "24 Jam" },
{ value: "tidak", label: "Tidak 24 Jam" }
]
}
],
validate: function (data) {
if (!data.nama || !data.no) {
return "Nama dan nomor SPBU wajib diisi.";
}
return "";
}
});
};
window.collectKemiskinanData = async function () {
return window.openEditModal({
title: "Tambah Data Kemiskinan",
subtitle: "Lengkapi data keluarga untuk titik yang dipilih di peta.",
fields: [
{ name: "nama_kepala_keluarga", label: "Nama Kepala Keluarga", value: "", required: true },
{ name: "jumlah_kk", label: "Jumlah KK", type: "number", min: 1, step: 1, value: 1, required: true },
{ name: "alamat", label: "Alamat", type: "textarea", value: "", required: true }
],
validate: function (data) {
if (!data.nama_kepala_keluarga || !data.alamat) {
return "Nama kepala keluarga dan alamat wajib diisi.";
}
if (!Number.isInteger(data.jumlah_kk) || data.jumlah_kk < 1) {
return "Jumlah KK harus angka minimal 1.";
}
return "";
}
});
};
window.collectMasjidData = async function () {
return window.openEditModal({
title: "Tambah Masjid",
subtitle: "Lengkapi identitas masjid untuk titik yang dipilih di peta.",
fields: [
{ name: "nama_masjid", label: "Nama Masjid", value: "", required: true },
{ name: "pic_masjid", label: "PIC Masjid", value: "", required: true },
{ name: "alamat", label: "Alamat Masjid", type: "textarea", value: "", required: true },
{ name: "radius", label: "Radius Jangkauan (meter)", type: "number", min: 100, max: 5000, step: 1, value: 500, required: true }
],
validate: function (data) {
if (!data.nama_masjid || !data.pic_masjid || !data.alamat) {
return "Nama, PIC, dan alamat masjid wajib diisi.";
}
if (!Number.isInteger(data.radius) || data.radius < 100 || data.radius > 5000) {
return "Radius harus angka antara 100 sampai 5000 meter.";
}
return "";
}
});
};
window.updateMapHint = function () {
var hint = document.getElementById("mapHint");
if (!hint) {
return;
}
if (appState.mode === "view") {
hint.textContent = "Mode lihat aktif. Data hanya ditampilkan di peta dan sidebar.";
return;
}
if (appState.activeType === "spbu") {
hint.textContent = "Mode edit SPBU aktif. Klik peta untuk menambahkan titik SPBU.";
return;
}
if (appState.activeType === "kemiskinan") {
hint.textContent = "Mode edit Kemiskinan aktif. Klik peta untuk menambahkan titik data kemiskinan.";
return;
}
if (appState.activeType === "masjid") {
hint.textContent = "Mode edit Masjid aktif. Klik peta untuk menambahkan titik masjid dengan radius awal 500 meter.";
return;
}
if (appState.activeType === "jalan") {
hint.textContent = "Mode edit Jalan aktif. Gunakan tool draw polyline pada peta.";
return;
}
hint.textContent = "Mode edit Kavling aktif. Gunakan tool draw polygon pada peta.";
};
window.updateToolbarState = function () {
var buttonStates = {
modeViewBtn: appState.mode === "view",
modeEditBtn: appState.mode === "edit",
typeSpbuBtn: appState.activeType === "spbu",
typeKemiskinanBtn: appState.activeType === "kemiskinan",
typeMasjidBtn: appState.activeType === "masjid",
typeJalanBtn: appState.activeType === "jalan",
typeKavlingBtn: appState.activeType === "kavling"
};
Object.keys(buttonStates).forEach(function (id) {
var button = document.getElementById(id);
if (button) {
button.classList.toggle("active", buttonStates[id]);
}
});
};
window.configureDrawControl = function () {
if (!window.map) {
return;
}
if (appState.drawControl) {
map.removeControl(appState.drawControl);
appState.drawControl = null;
}
if (appState.mode !== "edit" || appState.activeType === "spbu" || appState.activeType === "kemiskinan" || appState.activeType === "masjid") {
return;
}
var drawOptions = {
polyline: false,
polygon: false,
rectangle: false,
circle: false,
marker: false,
circlemarker: false
};
if (appState.activeType === "jalan") {
drawOptions.polyline = {
shapeOptions: {
color: "#dc3545",
weight: 4
}
};
}
if (appState.activeType === "kavling") {
drawOptions.polygon = {
allowIntersection: false,
shapeOptions: {
color: "#ff8c00",
weight: 3,
fillOpacity: 0.35
}
};
}
appState.drawControl = new L.Control.Draw({
edit: false,
draw: drawOptions
});
map.addControl(appState.drawControl);
};
window.setMode = function (mode) {
appState.mode = mode;
window.updateToolbarState();
window.updateMapHint();
window.configureDrawControl();
if (typeof window.loadAllData === "function") {
window.loadAllData();
}
};
window.setDataType = function (type) {
appState.activeType = type;
window.updateToolbarState();
window.updateMapHint();
window.configureDrawControl();
};
window.getLayerGroupByKey = function (key) {
if (key === "spbu24") {
return appState.layers.spbu24;
}
if (key === "spbuTidak") {
return appState.layers.spbuTidak;
}
if (key === "kemiskinan") {
return appState.layers.kemiskinan;
}
if (key === "masjid") {
return appState.layers.masjid;
}
if (key === "jalan") {
return appState.layers.jalan;
}
if (key === "kavling") {
return appState.layers.kavling;
}
return null;
};
window.setLayerVisibility = function (key, isVisible) {
appState.layerVisibility[key] = !!isVisible;
var layer = window.getLayerGroupByKey(key);
if (!layer || !window.map) {
return;
}
if (isVisible) {
if (!map.hasLayer(layer)) {
map.addLayer(layer);
}
return;
}
if (map.hasLayer(layer)) {
map.removeLayer(layer);
}
};
document.addEventListener("DOMContentLoaded", function () {
window.map = L.map("map").setView([-0.0263, 109.3425], 13);
appState.layers.spbu24 = L.layerGroup().addTo(map);
appState.layers.spbuTidak = L.layerGroup().addTo(map);
appState.layers.kemiskinan = L.layerGroup().addTo(map);
appState.layers.masjid = L.layerGroup().addTo(map);
appState.layers.jalan = L.layerGroup().addTo(map);
appState.layers.kavling = L.layerGroup().addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "&copy; OpenStreetMap"
}).addTo(map);
appState.layerControl = L.control.layers(null, {
"SPBU 24 Jam": appState.layers.spbu24,
"SPBU Tidak 24 Jam": appState.layers.spbuTidak,
"Kemiskinan": appState.layers.kemiskinan,
"Masjid": appState.layers.masjid,
"Jalan": appState.layers.jalan,
"Kavling": appState.layers.kavling
}).addTo(map);
map.on("overlayadd", function (event) {
if (event.layer === appState.layers.spbu24) {
appState.layerVisibility.spbu24 = true;
} else if (event.layer === appState.layers.spbuTidak) {
appState.layerVisibility.spbuTidak = true;
} else if (event.layer === appState.layers.kemiskinan) {
appState.layerVisibility.kemiskinan = true;
} else if (event.layer === appState.layers.masjid) {
appState.layerVisibility.masjid = true;
} else if (event.layer === appState.layers.jalan) {
appState.layerVisibility.jalan = true;
} else if (event.layer === appState.layers.kavling) {
appState.layerVisibility.kavling = true;
}
if (typeof window.refreshLayerFilterUI === "function") {
window.refreshLayerFilterUI();
}
});
map.on("overlayremove", function (event) {
if (event.layer === appState.layers.spbu24) {
appState.layerVisibility.spbu24 = false;
} else if (event.layer === appState.layers.spbuTidak) {
appState.layerVisibility.spbuTidak = false;
} else if (event.layer === appState.layers.kemiskinan) {
appState.layerVisibility.kemiskinan = false;
} else if (event.layer === appState.layers.masjid) {
appState.layerVisibility.masjid = false;
} else if (event.layer === appState.layers.jalan) {
appState.layerVisibility.jalan = false;
} else if (event.layer === appState.layers.kavling) {
appState.layerVisibility.kavling = false;
}
if (typeof window.refreshLayerFilterUI === "function") {
window.refreshLayerFilterUI();
}
});
map.on("click", async function (event) {
if (appState.mode !== "edit") {
return;
}
if (appState.activeType === "spbu") {
var spbuData = await window.collectSpbuData();
if (!spbuData) {
return;
}
try {
await window.postForm("php/create.php", {
nama: spbuData.nama,
no: spbuData.no,
status: spbuData.status,
lat: event.latlng.lat.toFixed(6),
lng: event.latlng.lng.toFixed(6)
});
await window.loadAllData();
alert("Data SPBU berhasil disimpan.");
} catch (error) {
alert("Gagal menyimpan SPBU: " + error.message);
}
return;
}
if (appState.activeType === "kemiskinan") {
var kemiskinanData = await window.collectKemiskinanData();
if (!kemiskinanData) {
return;
}
try {
await window.postForm("php/create_kemiskinan.php", {
nama_kepala_keluarga: kemiskinanData.nama_kepala_keluarga,
jumlah_kk: kemiskinanData.jumlah_kk,
alamat: kemiskinanData.alamat,
lat: event.latlng.lat.toFixed(6),
lng: event.latlng.lng.toFixed(6)
});
await window.loadAllData();
alert("Data kemiskinan berhasil disimpan.");
} catch (error) {
alert("Gagal menyimpan data kemiskinan: " + error.message);
}
return;
}
if (appState.activeType === "masjid") {
var masjidData = await window.collectMasjidData();
if (!masjidData) {
return;
}
try {
await window.postForm("php/create_masjid.php", {
nama_masjid: masjidData.nama_masjid,
pic_masjid: masjidData.pic_masjid,
alamat: masjidData.alamat,
radius: masjidData.radius,
lat: event.latlng.lat.toFixed(6),
lng: event.latlng.lng.toFixed(6)
});
await window.loadAllData();
alert("Data masjid berhasil disimpan.");
} catch (error) {
alert("Gagal menyimpan data masjid: " + error.message);
}
}
});
map.on(L.Draw.Event.CREATED, async function (event) {
var layer = event.layer;
if (appState.mode !== "edit") {
alert("Pindah ke mode edit sebelum menggambar data.");
return;
}
if (event.layerType === "polyline") {
if (appState.activeType !== "jalan") {
alert("Tipe aktif saat ini bukan Jalan.");
return;
}
var lineLatLngs = layer.getLatLngs();
var panjang = window.calculateLineLength(lineLatLngs);
var roadData = await window.collectRoadData();
if (!roadData) {
return;
}
try {
await window.postForm("php/create_line.php", {
nama: roadData.nama,
status: roadData.status,
panjang: panjang.toFixed(2),
geom: window.serializeGeometry(lineLatLngs)
});
await window.loadAllData();
alert("Data jalan berhasil disimpan.");
} catch (error) {
alert("Gagal menyimpan jalan: " + error.message);
}
return;
}
if (event.layerType === "polygon") {
if (appState.activeType !== "kavling") {
alert("Tipe aktif saat ini bukan Kavling.");
return;
}
var polygonLatLngs = layer.getLatLngs()[0] || [];
var luas = window.calculatePolygonArea(polygonLatLngs);
var landData = await window.collectPolygonData();
if (!landData) {
return;
}
try {
await window.postForm("php/create_polygon.php", {
nama: landData.nama,
status: landData.status,
luas: luas.toFixed(2),
geom: window.serializeGeometry(polygonLatLngs)
});
await window.loadAllData();
alert("Data kavling berhasil disimpan.");
} catch (error) {
alert("Gagal menyimpan kavling: " + error.message);
}
}
});
window.setMode("view");
window.setDataType("spbu");
setTimeout(function () {
map.invalidateSize();
}, 300);
if (typeof window.loadAllData === "function") {
window.loadAllData();
}
});