fix: update final code deployment
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
|
||||
|
||||
RUN a2enmod rewrite
|
||||
|
||||
COPY . /var/www/html/
|
||||
|
||||
RUN chown -R www-data:www-data /var/www/html \
|
||||
&& chmod -R 755 /var/www/html
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
include 'db2.php';
|
||||
require_once __DIR__ . '/includes/stats.php';
|
||||
|
||||
function toGeoJsonLine($pts) {
|
||||
$coords = array_map(fn($p) => [(float)$p[1], (float)$p[0]], $pts);
|
||||
return json_encode(['type' => 'LineString', 'coordinates' => $coords]);
|
||||
}
|
||||
function toGeoJsonPoly($pts) {
|
||||
$coords = array_map(fn($p) => [(float)$p[1], (float)$p[0]], $pts);
|
||||
if (count($coords) && $coords[0] !== $coords[count($coords)-1]) $coords[] = $coords[0];
|
||||
return json_encode(['type' => 'Polygon', 'coordinates' => [$coords]]);
|
||||
}
|
||||
function toGeoJsonPoint($lat, $lng) {
|
||||
return json_encode(['type' => 'Point', 'coordinates' => [(float)$lng, (float)$lat]]);
|
||||
}
|
||||
|
||||
// Deteksi POST yang melebihi post_max_size: PHP mengosongkan $_POST/$_FILES
|
||||
// padahal CONTENT_LENGTH > 0. Tanpa ini respons jadi kosong -> "Koneksi gagal!".
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST'
|
||||
&& empty($_POST) && empty($_FILES)
|
||||
&& (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
http_response_code(413);
|
||||
echo json_encode([
|
||||
'ok' => false,
|
||||
'msg' => 'Data terlalu besar (melebihi post_max_size=' . ini_get('post_max_size')
|
||||
. '). Kecilkan ukuran foto atau naikkan post_max_size.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? null;
|
||||
if (!$action) return;
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$a = $action;
|
||||
|
||||
if ($a === 'reverse') {
|
||||
$lat = (float)($_GET['lat'] ?? 0);
|
||||
$lon = (float)($_GET['lon'] ?? $_GET['lng'] ?? 0);
|
||||
if ($lat == 0 && $lon == 0) { echo json_encode(['error' => 'koordinat kosong']); exit; }
|
||||
$url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2&zoom=17&accept-language=id'
|
||||
. '&lat=' . urlencode($lat) . '&lon=' . urlencode($lon);
|
||||
// Nominatim mewajibkan User-Agent yang valid; browser tak bisa set ini, jadi diproxy dari server
|
||||
$ua = 'SIG2-JalanTanah/1.0 (kontak: webmaster@webgis-untan.xyz)';
|
||||
$body = false;
|
||||
if (function_exists('curl_init')) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_USERAGENT => $ua,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
if ($body === false) {
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'header' => "User-Agent: $ua\r\n",
|
||||
'timeout' => 8,
|
||||
]]);
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
}
|
||||
if ($body === false) { echo json_encode(['error' => 'gagal hubungi nominatim']); exit; }
|
||||
echo $body; exit;
|
||||
}
|
||||
|
||||
if ($a === 'search') {
|
||||
$q = '%' . $conn->real_escape_string($_GET['q'] ?? '') . '%';
|
||||
$tipe = $_GET['tipe'] ?? 'semua';
|
||||
$sf = $_GET['status_filter'] ?? '';
|
||||
$pmin = (float)($_GET['panjang_min'] ?? 0);
|
||||
$pmax = (float)($_GET['panjang_max'] ?? 0);
|
||||
$rusak_periode = $_GET['rusak_periode'] ?? 'semua';
|
||||
$out = [];
|
||||
if ($tipe === 'semua' || $tipe === 'jalan') {
|
||||
$w = "nama_jalan LIKE '$q'";
|
||||
if ($sf) $w .= " AND status_jalan='" . $conn->real_escape_string($sf) . "'";
|
||||
if ($pmin > 0) $w .= " AND panjang_meter>=$pmin";
|
||||
if ($pmax > 0) $w .= " AND panjang_meter<=$pmax";
|
||||
$rj = $conn->query("SELECT id,nama_jalan,status_jalan,panjang_meter,koordinat FROM jalan WHERE $w LIMIT 20");
|
||||
while ($row = $rj->fetch_assoc()) { $row['type'] = 'jalan'; $out[] = $row; }
|
||||
}
|
||||
if ($tipe === 'semua' || $tipe === 'parsil') {
|
||||
$w = "(nama_parsil LIKE '$q' OR nama_pemilik LIKE '$q')";
|
||||
if ($sf) $w .= " AND status_kepemilikan='" . $conn->real_escape_string($sf) . "'";
|
||||
$rp = $conn->query("SELECT id,nama_parsil,nama_pemilik,nomor_sertifikat,status_kepemilikan,luas_m2,koordinat FROM parsil WHERE $w LIMIT 20");
|
||||
while ($row = $rp->fetch_assoc()) { $row['type'] = 'parsil'; $out[] = $row; }
|
||||
}
|
||||
if ($tipe === 'semua' || $tipe === 'rusak') {
|
||||
$wR = "nama LIKE '$q'";
|
||||
if ($rusak_periode === '1bulan') $wR .= " AND tanggal >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)";
|
||||
elseif ($rusak_periode === '3tahun') $wR .= " AND tanggal >= DATE_SUB(CURDATE(), INTERVAL 3 YEAR)";
|
||||
$rr = $conn->query("SELECT id,nama,tanggal,latitude,longitude,keterangan,koordinat FROM jalan_rusak WHERE $wR ORDER BY tanggal DESC LIMIT 20");
|
||||
while ($row = $rr->fetch_assoc()) { $row['type'] = 'rusak'; $out[] = $row; }
|
||||
}
|
||||
echo json_encode($out); exit;
|
||||
}
|
||||
|
||||
if ($a === 'save_jalan' && isset($_GET['nama'], $_GET['status'], $_GET['ukuran'], $_GET['koordinat'])) {
|
||||
$nama = $conn->real_escape_string($_GET['nama']);
|
||||
$status = $conn->real_escape_string($_GET['status']);
|
||||
$panjang = (float)$_GET['ukuran'];
|
||||
$raw = json_decode($_GET['koordinat'], true);
|
||||
$geojson = $conn->real_escape_string(toGeoJsonLine($raw));
|
||||
$r = $conn->query("INSERT INTO jalan(nama_jalan,status_jalan,panjang_meter,koordinat) VALUES('$nama','$status','$panjang','$geojson')");
|
||||
echo json_encode(array_merge(['ok' => (bool)$r, 'id' => $conn->insert_id, 'msg' => $conn->error], getStats($conn))); exit;
|
||||
}
|
||||
if ($a === 'save_parsil' && isset($_GET['nama'], $_GET['status'], $_GET['ukuran'], $_GET['koordinat'])) {
|
||||
$nama = $conn->real_escape_string($_GET['nama']);
|
||||
$pemilik = $conn->real_escape_string($_GET['pemilik'] ?? '');
|
||||
$nosert = $conn->real_escape_string($_GET['nosert'] ?? '');
|
||||
$ket = $conn->real_escape_string($_GET['keterangan'] ?? '');
|
||||
$status = $conn->real_escape_string($_GET['status']);
|
||||
$luas = (float)$_GET['ukuran'];
|
||||
$raw = json_decode($_GET['koordinat'], true);
|
||||
$geojson = $conn->real_escape_string(toGeoJsonPoly($raw));
|
||||
$r = $conn->query("INSERT INTO parsil(nama_parsil,nama_pemilik,nomor_sertifikat,keterangan,status_kepemilikan,luas_m2,koordinat) VALUES('$nama','$pemilik','$nosert','$ket','$status','$luas','$geojson')");
|
||||
echo json_encode(array_merge(['ok' => (bool)$r, 'id' => $conn->insert_id, 'msg' => $conn->error], getStats($conn))); exit;
|
||||
}
|
||||
if ($a === 'save_rusak') {
|
||||
$pelapor = $conn->real_escape_string($_POST['pelapor'] ?? $_GET['pelapor'] ?? '') ?: 'Anonym';
|
||||
$nama_jalan = $conn->real_escape_string($_POST['nama_jalan'] ?? $_GET['nama_jalan'] ?? '');
|
||||
$tanggal = $conn->real_escape_string($_POST['tanggal'] ?? $_GET['tanggal'] ?? date('Y-m-d'));
|
||||
$lat = (float)($_POST['lat'] ?? $_GET['lat'] ?? 0);
|
||||
$lng = (float)($_POST['lng'] ?? $_GET['lng'] ?? 0);
|
||||
$ket = $conn->real_escape_string($_POST['keterangan'] ?? $_GET['keterangan'] ?? '');
|
||||
$foto = $conn->real_escape_string($_POST['foto'] ?? $_GET['foto'] ?? '');
|
||||
$namaFull = $conn->real_escape_string($pelapor . ($nama_jalan ? ' | Jalan: ' . $nama_jalan : ''));
|
||||
$geojson = $conn->real_escape_string(toGeoJsonPoint($lat, $lng));
|
||||
$r = $conn->query("INSERT INTO jalan_rusak(nama,tanggal,latitude,longitude,keterangan,foto,koordinat) VALUES('$namaFull','$tanggal','$lat','$lng','$ket','$foto','$geojson')");
|
||||
echo json_encode(array_merge(['ok' => (bool)$r, 'id' => $conn->insert_id, 'msg' => $conn->error, 'nama' => $namaFull], getStats($conn))); exit;
|
||||
}
|
||||
|
||||
if ($a === 'delete_jalan' && isset($_GET['id'])) {
|
||||
$conn->query("DELETE FROM jalan WHERE id=" . (int)$_GET['id']);
|
||||
echo json_encode(getStats($conn)); exit;
|
||||
}
|
||||
if ($a === 'delete_parsil' && isset($_GET['id'])) {
|
||||
$conn->query("DELETE FROM parsil WHERE id=" . (int)$_GET['id']);
|
||||
echo json_encode(getStats($conn)); exit;
|
||||
}
|
||||
if ($a === 'delete_rusak' && isset($_GET['id'])) {
|
||||
$conn->query("DELETE FROM jalan_rusak WHERE id=" . (int)$_GET['id']);
|
||||
echo json_encode(getStats($conn)); exit;
|
||||
}
|
||||
|
||||
if ($a === 'edit_jalan' && isset($_GET['id'], $_GET['nama'], $_GET['status'])) {
|
||||
$id = (int)$_GET['id'];
|
||||
$n = $conn->real_escape_string($_GET['nama']);
|
||||
$s = $conn->real_escape_string($_GET['status']);
|
||||
$coords = $_GET['koordinat'] ?? '';
|
||||
$pjg = 0;
|
||||
if ($coords) {
|
||||
$g = json_decode($coords, true);
|
||||
$pts = [];
|
||||
if (isset($g['type']) && $g['type'] === 'LineString') foreach ($g['coordinates'] as $c) $pts[] = ['lat' => $c[1], 'lng' => $c[0]];
|
||||
else foreach (json_decode($coords, true) as $p) $pts[] = ['lat' => $p[0], 'lng' => $p[1]];
|
||||
for ($i = 0; $i < count($pts) - 1; $i++) {
|
||||
$la1 = deg2rad($pts[$i]['lat']); $lo1 = deg2rad($pts[$i]['lng']);
|
||||
$la2 = deg2rad($pts[$i+1]['lat']); $lo2 = deg2rad($pts[$i+1]['lng']);
|
||||
$d = 2 * 6371000 * asin(sqrt(sin(($la2-$la1)/2)**2 + cos($la1)*cos($la2)*sin(($lo2-$lo1)/2)**2));
|
||||
$pjg += $d;
|
||||
}
|
||||
$ce = $conn->real_escape_string($coords);
|
||||
$conn->query("UPDATE jalan SET nama_jalan='$n',status_jalan='$s',panjang_meter='$pjg',koordinat='$ce' WHERE id=$id");
|
||||
} else {
|
||||
$conn->query("UPDATE jalan SET nama_jalan='$n',status_jalan='$s' WHERE id=$id");
|
||||
$pjg = $conn->query("SELECT panjang_meter FROM jalan WHERE id=$id")->fetch_assoc()['panjang_meter'];
|
||||
}
|
||||
echo json_encode(array_merge(getStats($conn), ['panjang' => round($pjg, 1)])); exit;
|
||||
}
|
||||
|
||||
if ($a === 'edit_parsil' && isset($_GET['id'], $_GET['nama'], $_GET['status'])) {
|
||||
$id = (int)$_GET['id'];
|
||||
$n = $conn->real_escape_string($_GET['nama']);
|
||||
$s = $conn->real_escape_string($_GET['status']);
|
||||
$pemilik = $conn->real_escape_string($_GET['pemilik'] ?? '');
|
||||
$nosert = $conn->real_escape_string($_GET['nosert'] ?? '');
|
||||
$ket = $conn->real_escape_string($_GET['keterangan'] ?? '');
|
||||
$coords = $_GET['koordinat'] ?? '';
|
||||
$luas = 0;
|
||||
if ($coords) {
|
||||
$g = json_decode($coords, true);
|
||||
$pts = isset($g['type']) && $g['type'] === 'Polygon'
|
||||
? array_map(fn($c) => ['lat' => $c[1], 'lng' => $c[0]], $g['coordinates'][0])
|
||||
: array_map(fn($p) => ['lat' => $p[0], 'lng' => $p[1]], json_decode($coords, true));
|
||||
$R = 6371000; $n2 = count($pts); $area = 0;
|
||||
for ($i = 0; $i < $n2; $i++) {
|
||||
$j = ($i + 1) % $n2;
|
||||
$xi = $pts[$i]['lng'] * M_PI/180 * $R * cos($pts[$i]['lat'] * M_PI/180);
|
||||
$yi = $pts[$i]['lat'] * M_PI/180 * $R;
|
||||
$xj = $pts[$j]['lng'] * M_PI/180 * $R * cos($pts[$j]['lat'] * M_PI/180);
|
||||
$yj = $pts[$j]['lat'] * M_PI/180 * $R;
|
||||
$area += ($xi * $yj - $xj * $yi);
|
||||
}
|
||||
$luas = abs($area / 2);
|
||||
$ce = $conn->real_escape_string($coords);
|
||||
$conn->query("UPDATE parsil SET nama_parsil='$n',status_kepemilikan='$s',nama_pemilik='$pemilik',nomor_sertifikat='$nosert',keterangan='$ket',luas_m2='$luas',koordinat='$ce' WHERE id=$id");
|
||||
} else {
|
||||
$conn->query("UPDATE parsil SET nama_parsil='$n',status_kepemilikan='$s',nama_pemilik='$pemilik',nomor_sertifikat='$nosert',keterangan='$ket' WHERE id=$id");
|
||||
$luas = $conn->query("SELECT luas_m2 FROM parsil WHERE id=$id")->fetch_assoc()['luas_m2'];
|
||||
}
|
||||
echo json_encode(array_merge(getStats($conn), ['luas' => round($luas, 1)])); exit;
|
||||
}
|
||||
|
||||
if ($a === 'edit_rusak' && isset($_GET['id'])) {
|
||||
$id = (int)$_GET['id'];
|
||||
$n = $conn->real_escape_string($_POST['nama'] ?? $_GET['nama'] ?? '');
|
||||
$tgl = $conn->real_escape_string($_POST['tanggal'] ?? $_GET['tanggal'] ?? '');
|
||||
$ket = $conn->real_escape_string($_POST['keterangan'] ?? $_GET['keterangan'] ?? '');
|
||||
$foto = $conn->real_escape_string($_POST['foto'] ?? $_GET['foto'] ?? '');
|
||||
$lat = isset($_POST['latitude']) ? (float)$_POST['latitude'] : (isset($_GET['latitude']) ? (float)$_GET['latitude'] : null);
|
||||
$lng = isset($_POST['longitude']) ? (float)$_POST['longitude'] : (isset($_GET['longitude']) ? (float)$_GET['longitude'] : null);
|
||||
if ($lat !== null && $lng !== null) {
|
||||
$geojson = $conn->real_escape_string(toGeoJsonPoint($lat, $lng));
|
||||
$conn->query("UPDATE jalan_rusak SET nama='$n',tanggal='$tgl',keterangan='$ket',foto='$foto',latitude='$lat',longitude='$lng',koordinat='$geojson' WHERE id=$id");
|
||||
} else {
|
||||
$conn->query("UPDATE jalan_rusak SET nama='$n',tanggal='$tgl',keterangan='$ket',foto='$foto' WHERE id=$id");
|
||||
}
|
||||
echo json_encode(array_merge(getStats($conn), ['ok' => true])); exit;
|
||||
}
|
||||
|
||||
echo json_encode(['error' => 'unknown']); exit;
|
||||
@@ -0,0 +1,442 @@
|
||||
/* Input mode: draw jalan / kavling / rusak on map */
|
||||
var drawMode = null, drawing = false, points = [], previewLayer = null, tempMarkers = [];
|
||||
var pendingKoord = null, pendingUkuran = 0, currentMode = null;
|
||||
var pendingRusakLatLng = null, fotoBase64 = '';
|
||||
|
||||
function setMode(m) {
|
||||
if (drawing) cancelDraw();
|
||||
drawMode = m;
|
||||
currentMode = m;
|
||||
|
||||
var bj = document.getElementById('btn-jalan');
|
||||
var bp = document.getElementById('btn-parsil');
|
||||
var br = document.getElementById('btn-rusak');
|
||||
if (bj) bj.className = m === 'jalan' ? 'active-jalan' : '';
|
||||
if (bp) bp.className = m === 'parsil' ? 'active-parsil' : '';
|
||||
if (br) br.className = m === 'rusak' ? 'active-rusak' : '';
|
||||
|
||||
var actions = document.getElementById('draw-actions');
|
||||
var hint = document.getElementById('hint');
|
||||
|
||||
if (m === 'rusak') {
|
||||
drawing = false;
|
||||
points = [];
|
||||
if (hint) hint.textContent = 'Klik titik di peta untuk menandai jalan rusak';
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
if (actions) actions.style.display = 'flex';
|
||||
['undo-btn', 'finish-btn'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.style.display = 'none';
|
||||
});
|
||||
var cancel = document.getElementById('cancel-btn');
|
||||
if (cancel) cancel.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
drawing = true;
|
||||
points = [];
|
||||
if (actions) actions.style.display = 'flex';
|
||||
['undo-btn', 'finish-btn', 'cancel-btn'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.style.display = 'block';
|
||||
});
|
||||
if (hint) {
|
||||
hint.textContent = m === 'jalan'
|
||||
? 'Klik peta untuk menambah titik jalan — minimal 2 titik'
|
||||
: 'Klik peta untuk menggambar area kavling — minimal 3 titik';
|
||||
}
|
||||
map.getContainer().style.cursor = 'crosshair';
|
||||
}
|
||||
|
||||
map.on('click', function (e) {
|
||||
if (currentMode === 'rusak') {
|
||||
pendingRusakLatLng = e.latlng;
|
||||
openRusakModal(e.latlng);
|
||||
return;
|
||||
}
|
||||
if (!drawing) return;
|
||||
points.push(e.latlng);
|
||||
var dot = L.circleMarker(e.latlng, {
|
||||
radius: 5,
|
||||
color: drawMode === 'jalan' ? '#2563eb' : '#7c3aed',
|
||||
fillColor: '#fff',
|
||||
fillOpacity: 1,
|
||||
weight: 2.5
|
||||
}).addTo(map);
|
||||
tempMarkers.push(dot);
|
||||
redrawPreview();
|
||||
});
|
||||
|
||||
function redrawPreview() {
|
||||
if (previewLayer) map.removeLayer(previewLayer);
|
||||
if (points.length < 2) return;
|
||||
var opts = { color: drawMode === 'jalan' ? '#2563eb' : '#7c3aed', weight: 2.5, dashArray: '7,5', opacity: 0.85 };
|
||||
previewLayer = drawMode === 'jalan'
|
||||
? L.polyline(points, opts).addTo(map)
|
||||
: L.polygon(points, Object.assign({}, opts, { fillOpacity: 0.1 })).addTo(map);
|
||||
}
|
||||
|
||||
function undoLast() {
|
||||
if (!points.length) return;
|
||||
points.pop();
|
||||
var last = tempMarkers.pop();
|
||||
if (last) map.removeLayer(last);
|
||||
redrawPreview();
|
||||
}
|
||||
|
||||
function cancelDraw() {
|
||||
drawing = false;
|
||||
points = [];
|
||||
if (previewLayer) { map.removeLayer(previewLayer); previewLayer = null; }
|
||||
tempMarkers.forEach(function (m) { map.removeLayer(m); });
|
||||
tempMarkers = [];
|
||||
map.getContainer().style.cursor = '';
|
||||
var actions = document.getElementById('draw-actions');
|
||||
if (actions) actions.style.display = 'none';
|
||||
['btn-jalan', 'btn-parsil', 'btn-rusak'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.className = '';
|
||||
});
|
||||
var hint = document.getElementById('hint');
|
||||
if (hint) hint.textContent = 'Pilih mode input di atas';
|
||||
currentMode = null;
|
||||
drawMode = null;
|
||||
pendingRusakLatLng = null;
|
||||
}
|
||||
|
||||
function finishShape() {
|
||||
if (drawMode === 'jalan' && points.length < 2) { showToast('Minimal 2 titik untuk jalan!', 'error'); return; }
|
||||
if (drawMode === 'parsil' && points.length < 3) { showToast('Minimal 3 titik untuk kavling!', 'error'); return; }
|
||||
drawing = false;
|
||||
map.getContainer().style.cursor = '';
|
||||
|
||||
var ukuran = 0;
|
||||
if (drawMode === 'jalan') {
|
||||
for (var i = 0; i < points.length - 1; i++) ukuran += points[i].distanceTo(points[i + 1]);
|
||||
ukuran = Math.round(ukuran * 10) / 10;
|
||||
document.getElementById('inp-ukuran').value = ukuran.toFixed(1) + ' meter';
|
||||
document.getElementById('form-title').textContent = 'Simpan Data Jalan';
|
||||
document.getElementById('form-sub').textContent = 'Lengkapi informasi ruas jalan';
|
||||
document.getElementById('form-icon').textContent = '🛣';
|
||||
document.getElementById('form-icon').className = 'modal-icon jalan';
|
||||
document.getElementById('label-nama').textContent = 'Nama Jalan';
|
||||
document.getElementById('label-status').textContent = 'Status Jalan';
|
||||
document.getElementById('label-ukuran').innerHTML = 'Panjang Jalan <span class="readonly-tag">Otomatis</span>';
|
||||
document.getElementById('inp-nama').placeholder = 'Contoh: Jl. Ahmad Yani';
|
||||
document.getElementById('inp-status').innerHTML =
|
||||
'<option value="Jalan Nasional">Jalan Nasional</option>' +
|
||||
'<option value="Jalan Provinsi">Jalan Provinsi</option>' +
|
||||
'<option value="Jalan Kabupaten">Jalan Kabupaten</option>';
|
||||
document.getElementById('parsil-extra-fields').style.display = 'none';
|
||||
} else {
|
||||
ukuran = calcArea(points);
|
||||
ukuran = Math.round(ukuran * 10) / 10;
|
||||
document.getElementById('inp-ukuran').value = ukuran.toFixed(1) + ' m²';
|
||||
document.getElementById('form-title').textContent = 'Simpan Data Kavling';
|
||||
document.getElementById('form-sub').textContent = 'Lengkapi informasi kavling tanah';
|
||||
document.getElementById('form-icon').textContent = '🏘';
|
||||
document.getElementById('form-icon').className = 'modal-icon parsil';
|
||||
document.getElementById('label-nama').textContent = 'Nama / Nomor Parsil';
|
||||
document.getElementById('label-status').textContent = 'Status Kepemilikan';
|
||||
document.getElementById('label-ukuran').innerHTML = 'Luas Area <span class="readonly-tag">Otomatis</span>';
|
||||
document.getElementById('inp-nama').placeholder = 'Contoh: Parsil A-01';
|
||||
document.getElementById('inp-status').innerHTML =
|
||||
'<option value="SHM">SHM — Sertifikat Hak Milik</option>' +
|
||||
'<option value="HGB">HGB — Hak Guna Bangunan</option>' +
|
||||
'<option value="HGU">HGU — Hak Guna Usaha</option>' +
|
||||
'<option value="HP">HP — Hak Pakai</option>';
|
||||
document.getElementById('parsil-extra-fields').style.display = 'block';
|
||||
document.getElementById('inp-pemilik').value = '';
|
||||
document.getElementById('inp-nosert').value = '';
|
||||
document.getElementById('inp-keterangan').value = '';
|
||||
}
|
||||
|
||||
pendingKoord = points.map(function (p) { return [p.lat, p.lng]; });
|
||||
pendingUkuran = ukuran;
|
||||
document.getElementById('inp-nama').value = '';
|
||||
document.getElementById('form-modal').classList.add('active');
|
||||
setTimeout(function () { document.getElementById('inp-nama').focus(); }, 100);
|
||||
}
|
||||
|
||||
function calcArea(latlngs) {
|
||||
var R = 6371000, n = latlngs.length, area = 0;
|
||||
if (n < 3) return 0;
|
||||
for (var i = 0; i < n; i++) {
|
||||
var j = (i + 1) % n;
|
||||
var xi = latlngs[i].lng * Math.PI / 180 * R * Math.cos(latlngs[i].lat * Math.PI / 180);
|
||||
var yi = latlngs[i].lat * Math.PI / 180 * R;
|
||||
var xj = latlngs[j].lng * Math.PI / 180 * R * Math.cos(latlngs[j].lat * Math.PI / 180);
|
||||
var yj = latlngs[j].lat * Math.PI / 180 * R;
|
||||
area += xi * yj - xj * yi;
|
||||
}
|
||||
return Math.abs(area / 2);
|
||||
}
|
||||
|
||||
function saveData() {
|
||||
var nama = document.getElementById('inp-nama').value.trim();
|
||||
var status = document.getElementById('inp-status').value;
|
||||
if (!nama) { showToast('Nama tidak boleh kosong!', 'error'); return; }
|
||||
|
||||
var btn = document.querySelector('#form-modal .btn-primary');
|
||||
btn.textContent = 'Menyimpan…';
|
||||
btn.disabled = true;
|
||||
|
||||
var params = {
|
||||
action: currentMode === 'jalan' ? 'save_jalan' : 'save_parsil',
|
||||
nama: nama,
|
||||
status: status,
|
||||
ukuran: pendingUkuran,
|
||||
koordinat: JSON.stringify(pendingKoord)
|
||||
};
|
||||
if (currentMode === 'parsil') {
|
||||
params.pemilik = document.getElementById('inp-pemilik').value.trim();
|
||||
params.nosert = document.getElementById('inp-nosert').value.trim();
|
||||
params.keterangan = document.getElementById('inp-keterangan').value.trim();
|
||||
}
|
||||
|
||||
fetch('api.php?' + new URLSearchParams(params))
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
btn.textContent = 'Simpan Data';
|
||||
btn.disabled = false;
|
||||
if (res.ok) {
|
||||
showToast('Data berhasil disimpan!', 'success');
|
||||
closeForm();
|
||||
var id = res.id;
|
||||
var ll = pendingKoord.map(function (p) { return [p[0], p[1]]; });
|
||||
if (currentMode === 'jalan') {
|
||||
var layer = L.polyline(ll, { color: WARNA_JALAN[status] || '#999', weight: 5, opacity: 0.9 }).addTo(map);
|
||||
layer.bindPopup(makeJalanPopup(id, nama, status, pendingUkuran.toFixed(1)), { maxWidth: 280 });
|
||||
layer.on('click', function () { lnavHighlightFromMap('jalan', id); });
|
||||
jalanLayers[id] = layer;
|
||||
jalanData[id] = { id: id, nama: nama, status: status, pjg: pendingUkuran.toFixed(1), ll: ll };
|
||||
} else {
|
||||
var opt = WARNA_PARSIL[status] || { color: '#999', fillColor: '#999', fillOpacity: 0.2, weight: 2 };
|
||||
var layer2 = L.polygon(ll, opt).addTo(map);
|
||||
layer2.bindPopup(makeParsilPopup(id, nama, params.pemilik || '', params.nosert || '', params.keterangan || '', status, pendingUkuran.toFixed(1)), { maxWidth: 280 });
|
||||
layer2.on('click', function () { lnavHighlightFromMap('parsil', id); });
|
||||
parsilLayers[id] = layer2;
|
||||
parsilData[id] = { id: id, nama: nama, pemilik: params.pemilik || '', nosert: params.nosert || '', ket: params.keterangan || '', status: status, luas: pendingUkuran.toFixed(1), ll: ll };
|
||||
}
|
||||
if (res.tj !== undefined) updateStats(res);
|
||||
cancelDraw();
|
||||
} else {
|
||||
showToast('Gagal: ' + (res.msg || 'unknown'), 'error');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
btn.textContent = 'Simpan Data';
|
||||
btn.disabled = false;
|
||||
showToast('Koneksi gagal!', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
document.getElementById('form-modal').classList.remove('active');
|
||||
}
|
||||
|
||||
document.getElementById('form-modal').addEventListener('click', function (e) {
|
||||
if (e.target === this) closeForm();
|
||||
});
|
||||
|
||||
function openRusakModal(latlng) {
|
||||
var d = new Date();
|
||||
var today = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
||||
document.getElementById('rusak-tanggal').value = today;
|
||||
document.getElementById('rusak-koordinat').value = latlng.lat.toFixed(6) + ', ' + latlng.lng.toFixed(6);
|
||||
document.getElementById('rusak-sub').textContent = latlng.lat.toFixed(5) + ', ' + latlng.lng.toFixed(5);
|
||||
document.getElementById('rusak-pelapor').value = '';
|
||||
document.getElementById('rusak-nama-jalan').value = 'Mencari nama jalan…';
|
||||
document.getElementById('rusak-ket').value = '';
|
||||
clearFoto();
|
||||
|
||||
fetch('api.php?action=reverse&lat=' + latlng.lat + '&lon=' + latlng.lng)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (geo) {
|
||||
var a = geo.address || {};
|
||||
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
|
||||
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
|
||||
})
|
||||
.catch(function () {
|
||||
document.getElementById('rusak-nama-jalan').value = '(Gagal deteksi nama jalan)';
|
||||
});
|
||||
|
||||
var badge = document.getElementById('gps-badge');
|
||||
badge.style.display = 'inline-flex';
|
||||
badge.className = 'gps-badge loading';
|
||||
badge.textContent = 'Mengambil GPS…';
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(function (pos) {
|
||||
badge.className = 'gps-badge';
|
||||
badge.textContent = 'GPS: ±' + Math.round(pos.coords.accuracy) + 'm akurasi';
|
||||
}, function () {
|
||||
badge.className = 'gps-badge';
|
||||
badge.textContent = 'Koordinat dari klik peta';
|
||||
}, { timeout: 5000 });
|
||||
} else {
|
||||
badge.className = 'gps-badge';
|
||||
badge.textContent = 'Koordinat dari klik peta';
|
||||
}
|
||||
|
||||
document.getElementById('rusak-modal').classList.add('active');
|
||||
setTimeout(function () { document.getElementById('rusak-pelapor').focus(); }, 100);
|
||||
}
|
||||
|
||||
function closeRusakModal() {
|
||||
document.getElementById('rusak-modal').classList.remove('active');
|
||||
fotoBase64 = '';
|
||||
pendingRusakLatLng = null;
|
||||
}
|
||||
|
||||
document.getElementById('rusak-modal').addEventListener('click', function (e) {
|
||||
if (e.target === this) closeRusakModal();
|
||||
});
|
||||
|
||||
function handleFoto(input) {
|
||||
var file = input.files[0];
|
||||
if (!file) return;
|
||||
if (file.size > 5 * 1024 * 1024) { showToast('Foto maks 5 MB!', 'error'); return; }
|
||||
|
||||
EXIF.getData(file, function() {
|
||||
var lat = EXIF.getTag(this, "GPSLatitude");
|
||||
var lon = EXIF.getTag(this, "GPSLongitude");
|
||||
var latRef = EXIF.getTag(this, "GPSLatitudeRef") || "N";
|
||||
var lonRef = EXIF.getTag(this, "GPSLongitudeRef") || "E";
|
||||
if (lat && lon) {
|
||||
function parseDms(dms) {
|
||||
if (!dms || dms.length < 3) return null;
|
||||
var d = (typeof dms[0] === 'object' && dms[0].denominator) ? dms[0].numerator / dms[0].denominator : Number(dms[0]);
|
||||
var m = (typeof dms[1] === 'object' && dms[1].denominator) ? dms[1].numerator / dms[1].denominator : Number(dms[1]);
|
||||
var s = (typeof dms[2] === 'object' && dms[2].denominator) ? dms[2].numerator / dms[2].denominator : Number(dms[2]);
|
||||
return d + (m / 60) + (s / 3600);
|
||||
}
|
||||
|
||||
var latDec = parseDms(lat);
|
||||
var lonDec = parseDms(lon);
|
||||
|
||||
if (latDec !== null && lonDec !== null && !isNaN(latDec) && !isNaN(lonDec)) {
|
||||
if (latRef === "S") latDec = -latDec;
|
||||
if (lonRef === "W") lonDec = -lonDec;
|
||||
pendingRusakLatLng = {lat: latDec, lng: lonDec};
|
||||
document.getElementById('rusak-koordinat').value = latDec.toFixed(6) + ', ' + lonDec.toFixed(6);
|
||||
showToast('Geotagging terdeteksi!', 'success');
|
||||
|
||||
var badge = document.getElementById('gps-badge');
|
||||
if(badge) {
|
||||
badge.style.display = 'inline-flex';
|
||||
badge.className = 'gps-badge';
|
||||
badge.textContent = 'GPS dari Foto';
|
||||
}
|
||||
|
||||
fetch('api.php?action=reverse&lat=' + latDec + '&lon=' + lonDec)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (geo) {
|
||||
var a = geo.address || {};
|
||||
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
|
||||
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
// Kompres & resize foto agar payload kecil (hindari post_max_size & batas kolom DB)
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var maxDim = 1280;
|
||||
var w = img.width, h = img.height;
|
||||
if (w > h && w > maxDim) { h = Math.round(h * maxDim / w); w = maxDim; }
|
||||
else if (h >= w && h > maxDim) { w = Math.round(w * maxDim / h); h = maxDim; }
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = w; canvas.height = h;
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||||
fotoBase64 = canvas.toDataURL('image/jpeg', 0.7);
|
||||
var preview = document.getElementById('photo-preview');
|
||||
preview.src = fotoBase64;
|
||||
preview.style.display = 'block';
|
||||
document.getElementById('foto-clear-btn').style.display = 'flex';
|
||||
};
|
||||
img.onerror = function () {
|
||||
// Fallback bila gambar tak bisa di-decode (mis. format tak didukung canvas)
|
||||
fotoBase64 = e.target.result;
|
||||
var preview = document.getElementById('photo-preview');
|
||||
preview.src = fotoBase64;
|
||||
preview.style.display = 'block';
|
||||
document.getElementById('foto-clear-btn').style.display = 'flex';
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function clearFoto() {
|
||||
fotoBase64 = '';
|
||||
document.getElementById('photo-preview').style.display = 'none';
|
||||
document.getElementById('photo-preview').src = '';
|
||||
document.getElementById('foto-clear-btn').style.display = 'none';
|
||||
document.getElementById('foto-file').value = '';
|
||||
document.getElementById('foto-cam').value = '';
|
||||
}
|
||||
|
||||
function saveRusak() {
|
||||
if (!pendingRusakLatLng) { showToast('Pilih titik di peta dulu!', 'error'); return; }
|
||||
var pelapor = document.getElementById('rusak-pelapor').value.trim() || 'Anonym';
|
||||
var namaJalan = document.getElementById('rusak-nama-jalan').value.trim();
|
||||
var tanggal = document.getElementById('rusak-tanggal').value;
|
||||
var ket = document.getElementById('rusak-ket').value.trim();
|
||||
var lat = pendingRusakLatLng.lat;
|
||||
var lng = pendingRusakLatLng.lng;
|
||||
|
||||
var btn = document.getElementById('rusak-save-btn');
|
||||
btn.textContent = 'Menyimpan…';
|
||||
btn.disabled = true;
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('action', 'save_rusak');
|
||||
formData.append('pelapor', pelapor);
|
||||
formData.append('nama_jalan', namaJalan);
|
||||
formData.append('tanggal', tanggal);
|
||||
formData.append('lat', lat);
|
||||
formData.append('lng', lng);
|
||||
formData.append('keterangan', ket);
|
||||
formData.append('foto', fotoBase64);
|
||||
|
||||
fetch('api.php', { method: 'POST', body: formData })
|
||||
.then(function (r) {
|
||||
return r.text().then(function (txt) {
|
||||
var res;
|
||||
try { res = JSON.parse(txt); }
|
||||
catch (e) {
|
||||
// Respons bukan JSON: tampilkan status & potongan isi supaya jelas penyebabnya
|
||||
var info = txt.trim() ? txt.trim().slice(0, 200) : '(respons kosong)';
|
||||
throw new Error('HTTP ' + r.status + ' — ' + info);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
})
|
||||
.then(function (res) {
|
||||
btn.textContent = 'Simpan Titik';
|
||||
btn.disabled = false;
|
||||
if (res.ok) {
|
||||
showToast('Titik jalan rusak disimpan!', 'success');
|
||||
var savedFoto = fotoBase64; // simpan dulu: closeRusakModal() me-reset fotoBase64
|
||||
closeRusakModal();
|
||||
var id = res.id;
|
||||
var nama = res.nama || pelapor;
|
||||
var layer = makeRusakLayer(id, nama, tanggal, lat, lng, ket, savedFoto, 'recent', false);
|
||||
rusakLayers[id] = layer;
|
||||
rusakData[id] = { id: id, nama: nama, tgl: tanggal, lat: lat, lng: lng, ket: ket, foto: savedFoto, age: 'recent', recurring: false };
|
||||
if (res.tr !== undefined) updateStats(res);
|
||||
cancelDraw();
|
||||
} else {
|
||||
showToast('Gagal: ' + (res.msg || 'unknown'), 'error');
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
btn.textContent = 'Simpan Titik';
|
||||
btn.disabled = false;
|
||||
showToast('Gagal simpan: ' + (err && err.message ? err.message : 'koneksi gagal'), 'error');
|
||||
console.error('saveRusak error:', err);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SIG Jalan Tanah — shared layout tokens (main styles in index.php) */
|
||||
:root {
|
||||
--bg: #f1f5f9;
|
||||
--white: #fff;
|
||||
--border: #e2e8f0;
|
||||
--text: #0f172a;
|
||||
--text2: #475569;
|
||||
--text3: #94a3b8;
|
||||
--blue: #1d4ed8;
|
||||
--sidebar-w: 320px;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
define('DB_HOST', getenv('DB_HOST'));
|
||||
define('DB_USER', getenv('DB_USER'));
|
||||
define('DB_PASS', getenv('DB_PASS'));
|
||||
define('DB_NAME', getenv('DB_NAME'));
|
||||
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||
$conn->set_charset("utf8");
|
||||
if ($conn->connect_error) {
|
||||
die("Koneksi gagal: " . $conn->connect_error);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
require_once 'config.php';
|
||||
?>
|
||||
@@ -0,0 +1,115 @@
|
||||
<style>
|
||||
#confirm-overlay {
|
||||
display: none; position: fixed; inset: 0; z-index: 99999;
|
||||
background: rgba(15,23,42,.4); backdrop-filter: blur(2px);
|
||||
align-items: center; justify-content: center; padding: 20px;
|
||||
}
|
||||
#confirm-overlay.show { display: flex; }
|
||||
#confirm-overlay.confirm-top {
|
||||
align-items: flex-start; justify-content: center;
|
||||
padding-top: 72px; background: transparent; backdrop-filter: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
#confirm-box {
|
||||
background: #fff; border: 1px solid #e2e8f0; border-radius: 12px;
|
||||
width: 100%; max-width: 400px;
|
||||
box-shadow: 0 16px 48px rgba(15,23,42,.14);
|
||||
animation: confirmIn .18s ease; overflow: hidden;
|
||||
}
|
||||
#confirm-overlay.confirm-top #confirm-box {
|
||||
pointer-events: all; max-width: 460px;
|
||||
box-shadow: 0 8px 28px rgba(15,23,42,.12);
|
||||
}
|
||||
@keyframes confirmIn { from { opacity:0; transform: translateY(-6px); } to { opacity:1; transform: none; } }
|
||||
#confirm-box .cf-head {
|
||||
display: flex; gap: 14px; padding: 20px 22px 0; align-items: flex-start;
|
||||
}
|
||||
#confirm-box .cf-icon {
|
||||
width: 40px; height: 40px; border-radius: 10px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
#confirm-box .cf-icon.warn { background: #fffbeb; border: 1px solid #fde68a; }
|
||||
#confirm-box .cf-icon.danger { background: #fef2f2; border: 1px solid #fecaca; }
|
||||
#confirm-box .cf-icon.info { background: #eff6ff; border: 1px solid #bfdbfe; }
|
||||
#confirm-box .cf-icon.move { background: #eff6ff; border: 1px solid #bfdbfe; }
|
||||
#confirm-box .cf-text { flex: 1; min-width: 0; }
|
||||
#confirm-box .cf-title { font-size: 15px; font-weight: 700; color: #0f172a; margin-bottom: 4px; }
|
||||
#confirm-box .cf-msg { font-size: 13px; color: #64748b; line-height: 1.55; }
|
||||
#confirm-box .cf-actions {
|
||||
display: flex; gap: 8px; padding: 18px 22px 20px; justify-content: flex-end;
|
||||
}
|
||||
#confirm-box .cf-btn {
|
||||
padding: 9px 16px; border-radius: 8px; font-size: 13px;
|
||||
font-weight: 600; cursor: pointer; border: 1px solid transparent;
|
||||
font-family: inherit; transition: background .15s;
|
||||
}
|
||||
#confirm-box .cf-cancel { background: #fff; color: #475569; border-color: #e2e8f0; }
|
||||
#confirm-box .cf-cancel:hover { background: #f8fafc; }
|
||||
#confirm-box .cf-ok { background: #1d4ed8; color: #fff; }
|
||||
#confirm-box .cf-ok:hover { background: #1e40af; }
|
||||
#confirm-box .cf-ok.danger { background: #dc2626; }
|
||||
#confirm-box .cf-ok.danger:hover { background: #b91c1c; }
|
||||
</style>
|
||||
<div id="confirm-overlay" role="dialog" aria-modal="true">
|
||||
<div id="confirm-box">
|
||||
<div class="cf-head">
|
||||
<div class="cf-icon warn" id="cf-icon"></div>
|
||||
<div class="cf-text">
|
||||
<div class="cf-title" id="cf-title">Konfirmasi</div>
|
||||
<div class="cf-msg" id="cf-msg"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cf-actions">
|
||||
<button type="button" class="cf-btn cf-cancel" id="cf-cancel">Batal</button>
|
||||
<button type="button" class="cf-btn cf-ok" id="cf-ok">Lanjutkan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var overlay = document.getElementById('confirm-overlay');
|
||||
var iconEl = document.getElementById('cf-icon');
|
||||
var titleEl = document.getElementById('cf-title');
|
||||
var msgEl = document.getElementById('cf-msg');
|
||||
var btnOk = document.getElementById('cf-ok');
|
||||
var btnCancel = document.getElementById('cf-cancel');
|
||||
var resolver = null;
|
||||
|
||||
var icons = {
|
||||
warn: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" stroke-linecap="round"><path d="M12 9v4m0 4h.01"/><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>',
|
||||
danger: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/></svg>',
|
||||
move: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round"><path d="M12 2v20M2 12h20"/><circle cx="12" cy="12" r="3"/></svg>'
|
||||
};
|
||||
|
||||
function closeConfirm(result) {
|
||||
overlay.classList.remove('show', 'confirm-top');
|
||||
document.body.style.overflow = '';
|
||||
if (resolver) { var r = resolver; resolver = null; r(result); }
|
||||
}
|
||||
|
||||
btnCancel.addEventListener('click', function(){ closeConfirm(false); });
|
||||
btnOk.addEventListener('click', function(){ closeConfirm(true); });
|
||||
overlay.addEventListener('click', function(e){ if (e.target === overlay && !overlay.classList.contains('confirm-top')) closeConfirm(false); });
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (!overlay.classList.contains('show')) return;
|
||||
if (e.key === 'Escape') closeConfirm(false);
|
||||
});
|
||||
|
||||
window.showConfirm = function(opts) {
|
||||
opts = opts || {};
|
||||
var danger = !!opts.danger;
|
||||
var kind = danger ? 'danger' : (opts.icon || 'warn');
|
||||
iconEl.className = 'cf-icon ' + kind;
|
||||
iconEl.innerHTML = icons[kind] || icons.warn;
|
||||
titleEl.textContent = opts.title || 'Konfirmasi';
|
||||
msgEl.innerHTML = opts.message || 'Apakah Anda yakin?';
|
||||
btnOk.textContent = opts.confirmText || (danger ? 'Ya, Hapus' : 'Lanjutkan');
|
||||
btnCancel.textContent = opts.cancelText || 'Batal';
|
||||
btnOk.classList.toggle('danger', danger);
|
||||
overlay.classList.toggle('confirm-top', opts.position === 'top');
|
||||
if (opts.position !== 'top') document.body.style.overflow = 'hidden';
|
||||
overlay.classList.add('show');
|
||||
return new Promise(function(resolve){ resolver = resolve; });
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
function getStats($conn) {
|
||||
$tj = $conn->query("SELECT COUNT(*) c FROM jalan")->fetch_assoc()['c'];
|
||||
$nas = $conn->query("SELECT COUNT(*) c FROM jalan WHERE status_jalan='Jalan Nasional'")->fetch_assoc()['c'];
|
||||
$pro = $conn->query("SELECT COUNT(*) c FROM jalan WHERE status_jalan='Jalan Provinsi'")->fetch_assoc()['c'];
|
||||
$kab = $conn->query("SELECT COUNT(*) c FROM jalan WHERE status_jalan='Jalan Kabupaten'")->fetch_assoc()['c'];
|
||||
$tp = $conn->query("SELECT COUNT(*) c FROM parsil")->fetch_assoc()['c'];
|
||||
$shm = $conn->query("SELECT COUNT(*) c FROM parsil WHERE status_kepemilikan='SHM'")->fetch_assoc()['c'];
|
||||
$hgb = $conn->query("SELECT COUNT(*) c FROM parsil WHERE status_kepemilikan='HGB'")->fetch_assoc()['c'];
|
||||
$hgu = $conn->query("SELECT COUNT(*) c FROM parsil WHERE status_kepemilikan='HGU'")->fetch_assoc()['c'];
|
||||
$hp = $conn->query("SELECT COUNT(*) c FROM parsil WHERE status_kepemilikan='HP'")->fetch_assoc()['c'];
|
||||
$tr = $conn->query("SELECT COUNT(*) c FROM jalan_rusak")->fetch_assoc()['c'];
|
||||
return compact('tj','nas','pro','kab','tp','shm','hgb','hgu','hp','tr');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user