add files and folders
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
FROM php:8.2-apache
|
||||
|
||||
RUN docker-php-ext-install mysqli pdo pdo_mysql \
|
||||
&& a2enmod rewrite
|
||||
|
||||
COPY . /var/www/html/
|
||||
|
||||
RUN mkdir -p /var/www/html/uploads \
|
||||
&& chown -R www-data:www-data /var/www/html/uploads \
|
||||
&& chown -R www-data:www-data /var/www/html
|
||||
@@ -0,0 +1,32 @@
|
||||
# WebGIS Sebaran Penduduk Miskin + Rumah Ibadah
|
||||
|
||||
Stack: PHP (XAMPP), MySQL/MariaDB, LeafletJS + Leaflet.draw (tanpa framework).
|
||||
|
||||
## Fitur
|
||||
- Peta interaktif (fullscreen) + layer: rumah ibadah, radius, penduduk miskin.
|
||||
- CRUD lewat aksi di peta (klik untuk point, popup untuk edit/hapus, edit lokasi + drag).
|
||||
- Reverse geocoding (server proxy `php/reverse_geocode.php` ke Nominatim).
|
||||
- Auto-relasi penduduk miskin → rumah ibadah terdekat **dalam radius** (jarak dihitung otomatis).
|
||||
- Marker penduduk miskin:
|
||||
- Merah: Belum dibantu
|
||||
- Kuning: Menunggu verifikasi
|
||||
- Hijau: Sudah dibantu
|
||||
- Outline: menunjukkan inside/outside radius rumah ibadah
|
||||
- Form penduduk miskin mendukung anggota keluarga (dinamis) + upload bukti (jpg/png/webp/pdf).
|
||||
- Admin panel (login) + CRUD dasar + import/export CSV.
|
||||
|
||||
## Instalasi (XAMPP)
|
||||
1. Pastikan folder project berada di `htdocs`, misalnya: `C:\\xampp\\htdocs\\webgis-spbu`.
|
||||
2. Buat database `webgis_spbu` di phpMyAdmin.
|
||||
3. Import schema: `sql/schema_webgis_spbu.sql`.
|
||||
4. Sesuaikan konfigurasi DB di `php/koneksi.php`.
|
||||
5. Jalankan Apache + MySQL di XAMPP.
|
||||
6. Buka aplikasi:
|
||||
- WebGIS: `http://localhost/webgis-spbu/`
|
||||
- Admin: `http://localhost/webgis-spbu/admin/login.php`
|
||||
- Jika belum ada user, jalankan sekali: `http://localhost/webgis-spbu/admin/init_admin.php`
|
||||
|
||||
## Catatan Upload Bukti
|
||||
- File disimpan ke `uploads/bukti/`.
|
||||
- Pastikan folder ini writable oleh Apache.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user']) && is_array($_SESSION['user']);
|
||||
}
|
||||
|
||||
function require_login() {
|
||||
if (!is_logged_in()) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function current_user() {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
require_once __DIR__ . '/_auth.php';
|
||||
|
||||
function h($v) { return htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8'); }
|
||||
|
||||
function render_header($title) {
|
||||
$u = current_user();
|
||||
$name = $u ? h($u['name'] ?? $u['username'] ?? '') : '';
|
||||
echo '<!doctype html><html lang="id"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
|
||||
echo '<title>' . h($title) . '</title>';
|
||||
echo '<link rel="stylesheet" href="../css/style.css">';
|
||||
echo '<style>
|
||||
body{background:#0b1220;margin:0}
|
||||
.admin-shell{max-width:1100px;margin:0 auto;padding:22px}
|
||||
.admin-top{display:flex;align-items:center;justify-content:space-between;color:#e5e7eb;margin-bottom:16px}
|
||||
.admin-card{background:#fff;border-radius:18px;box-shadow:0 18px 45px rgba(0,0,0,.25);padding:18px}
|
||||
.admin-nav a{color:#e5e7eb;text-decoration:none;margin-right:12px;font-weight:800}
|
||||
.admin-nav a:hover{text-decoration:underline}
|
||||
table{width:100%;border-collapse:collapse}
|
||||
th,td{padding:10px 10px;border-bottom:1px solid #e2e8f0;font-size:14px;text-align:left;vertical-align:top}
|
||||
th{font-weight:900}
|
||||
.tag{display:inline-block;padding:4px 8px;border-radius:999px;background:#eef2ff;color:#1d4ed8;font-weight:900;font-size:12px}
|
||||
.actions{display:flex;gap:8px;flex-wrap:wrap}
|
||||
.actions a,.actions button{border:0;border-radius:12px;padding:8px 10px;font-weight:900;cursor:pointer}
|
||||
.actions a{background:#0f172a;color:#fff;text-decoration:none}
|
||||
.actions button.danger{background:#dc2626;color:#fff}
|
||||
.actions a.secondary{background:#e2e8f0;color:#0f172a}
|
||||
</style></head><body>';
|
||||
echo '<div class="admin-shell">';
|
||||
echo '<div class="admin-top"><div><div style="font-size:22px;font-weight:900;color:#fff">Admin Panel</div><div class="admin-nav">';
|
||||
echo '<a href="index.php">Dashboard</a><a href="rumah_ibadah.php">Rumah Ibadah</a><a href="penduduk_miskin.php">Penduduk Miskin</a>';
|
||||
echo '</div></div><div style="color:#cbd5e1;font-weight:800">' . $name . ' <a href="logout.php" style="color:#93c5fd;text-decoration:none">Logout</a></div></div>';
|
||||
}
|
||||
|
||||
function render_footer() {
|
||||
echo '</div></body></html>';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login();
|
||||
|
||||
// Stats
|
||||
$counts = [
|
||||
'rumah_ibadah' => 0,
|
||||
'penduduk' => 0,
|
||||
'dibantu' => 0,
|
||||
'belum' => 0,
|
||||
'verifikasi' => 0
|
||||
];
|
||||
|
||||
try {
|
||||
$counts['rumah_ibadah'] = (int)$pdo->query("SELECT COUNT(*) FROM rumah_ibadah_points")->fetchColumn();
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('status_bantuan', $cols, true);
|
||||
$counts['penduduk'] = (int)$pdo->query("SELECT COUNT(*) FROM pemukiman_miskin_points")->fetchColumn();
|
||||
if ($hasNew) {
|
||||
$counts['dibantu'] = (int)$pdo->query("SELECT COUNT(*) FROM pemukiman_miskin_points WHERE status_bantuan='Sudah dibantu'")->fetchColumn();
|
||||
$counts['belum'] = (int)$pdo->query("SELECT COUNT(*) FROM pemukiman_miskin_points WHERE status_bantuan='Belum dibantu'")->fetchColumn();
|
||||
$counts['verifikasi'] = (int)$pdo->query("SELECT COUNT(*) FROM pemukiman_miskin_points WHERE status_bantuan='Menunggu verifikasi'")->fetchColumn();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
render_header('Admin Dashboard');
|
||||
?>
|
||||
<div class="admin-card">
|
||||
<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px">
|
||||
<div style="background:#0b1220;border-radius:18px;padding:14px;color:#fff">
|
||||
<div style="font-weight:900;font-size:28px"><?= (int)$counts['rumah_ibadah'] ?></div>
|
||||
<div style="color:#cbd5e1;font-weight:800">Rumah Ibadah</div>
|
||||
</div>
|
||||
<div style="background:#0b1220;border-radius:18px;padding:14px;color:#fff">
|
||||
<div style="font-weight:900;font-size:28px"><?= (int)$counts['penduduk'] ?></div>
|
||||
<div style="color:#cbd5e1;font-weight:800">Penduduk Miskin</div>
|
||||
</div>
|
||||
<div style="background:#052e1a;border-radius:18px;padding:14px;color:#fff">
|
||||
<div style="font-weight:900;font-size:28px"><?= (int)$counts['dibantu'] ?></div>
|
||||
<div style="color:#bbf7d0;font-weight:800">Sudah dibantu</div>
|
||||
</div>
|
||||
<div style="background:#3b0a0a;border-radius:18px;padding:14px;color:#fff">
|
||||
<div style="font-weight:900;font-size:28px"><?= (int)$counts['belum'] ?></div>
|
||||
<div style="color:#fecaca;font-weight:800">Belum dibantu</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:14px;color:#475569;font-weight:800">
|
||||
Menu admin ini melengkapi WebGIS utama: `index.html`.
|
||||
</div>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
username VARCHAR(60) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL DEFAULT 'operator',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
");
|
||||
|
||||
$count = (int)$pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||
if ($count > 0) {
|
||||
echo "Users sudah ada. Silakan login di <a href='login.php'>login.php</a>.";
|
||||
exit;
|
||||
}
|
||||
|
||||
$username = 'admin';
|
||||
$password = 'admin123';
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (name, username, password_hash, role) VALUES (:n,:u,:p,'admin')");
|
||||
$stmt->execute([':n' => 'Administrator', ':u' => $username, ':p' => $hash]);
|
||||
|
||||
echo "Admin dibuat. Username: <b>admin</b>, Password: <b>admin123</b>. Lanjut login: <a href='login.php'>login.php</a>";
|
||||
} catch (Exception $e) {
|
||||
echo "Gagal init admin: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../php/koneksi.php';
|
||||
require_once __DIR__ . '/_auth.php';
|
||||
|
||||
if (is_logged_in()) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT id, name, username, password_hash, role FROM users WHERE username = :u LIMIT 1");
|
||||
$stmt->execute([':u' => $username]);
|
||||
$u = $stmt->fetch();
|
||||
if ($u && password_verify($password, $u['password_hash'])) {
|
||||
$_SESSION['user'] = ['id' => (int)$u['id'], 'name' => $u['name'], 'username' => $u['username'], 'role' => $u['role']];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
$error = 'Username atau password salah.';
|
||||
} catch (Exception $e) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Login Admin</title>
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
<style>
|
||||
body{background:#0b1220;margin:0;display:grid;place-items:center;min-height:100vh;padding:18px}
|
||||
.card{background:#fff;border-radius:22px;box-shadow:0 18px 45px rgba(0,0,0,.25);width:100%;max-width:420px;padding:18px}
|
||||
.title{font-weight:900;font-size:22px;margin:0 0 4px}
|
||||
.sub{color:#64748b;margin:0 0 14px;font-weight:700}
|
||||
.err{background:#fef2f2;border:1px solid #fecaca;color:#991b1b;padding:10px 12px;border-radius:14px;font-weight:800;margin-bottom:12px}
|
||||
.btnx{width:100%;margin-top:12px}
|
||||
a{color:#2563eb;text-decoration:none;font-weight:900}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="title">Login Admin</div>
|
||||
<div class="sub">Jika belum ada user, jalankan <a href="init_admin.php">init_admin.php</a>.</div>
|
||||
<?php if ($error): ?><div class="err"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<form method="post">
|
||||
<label>Username</label>
|
||||
<input name="username" required>
|
||||
<label style="margin-top:10px;display:block;">Password</label>
|
||||
<input name="password" type="password" required>
|
||||
<button class="btn btn-primary btnx" type="submit">Masuk</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_auth.php';
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login();
|
||||
|
||||
function csv_cell($v) {
|
||||
$s = (string)($v ?? '');
|
||||
$s = str_replace('"', '""', $s);
|
||||
return '"' . $s . '"';
|
||||
}
|
||||
|
||||
// Export CSV
|
||||
if (isset($_GET['export'])) {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="penduduk_miskin.csv"');
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
$out = fopen('php://output', 'w');
|
||||
if ($hasNew) {
|
||||
fputcsv($out, ['id','kk_nama','nik','jumlah_anggota','lat','lng','alamat','kelurahan','kecamatan','status_bantuan','jenis_bantuan','tanggal_bantuan','rumah_ibadah_id','jarak_meter']);
|
||||
$rows = $pdo->query("SELECT id, kk_nama, nik, jumlah_anggota, latitude, longitude, address, kelurahan, kecamatan, status_bantuan, jenis_bantuan, tanggal_bantuan, rumah_ibadah_id, jarak_meter FROM pemukiman_miskin_points ORDER BY id ASC")->fetchAll();
|
||||
foreach ($rows as $r) {
|
||||
fputcsv($out, [
|
||||
$r['id'], $r['kk_nama'], $r['nik'], $r['jumlah_anggota'],
|
||||
$r['latitude'], $r['longitude'], $r['address'], $r['kelurahan'], $r['kecamatan'],
|
||||
$r['status_bantuan'], $r['jenis_bantuan'], $r['tanggal_bantuan'],
|
||||
$r['rumah_ibadah_id'], $r['jarak_meter']
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
fputcsv($out, ['id','nama','lat','lng','alamat','rumah_ibadah_id']);
|
||||
$rows = $pdo->query("SELECT id, nama, latitude, longitude, address, rumah_ibadah_id FROM pemukiman_miskin_points ORDER BY id ASC")->fetchAll();
|
||||
foreach ($rows as $r) fputcsv($out, [$r['id'],$r['nama'],$r['latitude'],$r['longitude'],$r['address'],$r['rumah_ibadah_id']]);
|
||||
}
|
||||
fclose($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
$msg = '';
|
||||
$err = '';
|
||||
|
||||
// Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = (int)$_GET['delete'];
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM pemukiman_miskin_points WHERE id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$msg = 'Berhasil dihapus.';
|
||||
} catch (Exception $e) {
|
||||
$err = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Import CSV (minimal)
|
||||
if (isset($_POST['import_csv'])) {
|
||||
if (!isset($_FILES['csv_file']) || ($_FILES['csv_file']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
$err = 'CSV tidak ditemukan.';
|
||||
} else {
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
$fp = fopen($_FILES['csv_file']['tmp_name'], 'r');
|
||||
$header = fgetcsv($fp);
|
||||
if (!$header) throw new Exception('Header CSV kosong.');
|
||||
$map = [];
|
||||
foreach ($header as $i => $hcol) $map[strtolower(trim($hcol))] = $i;
|
||||
$pdo->beginTransaction();
|
||||
$ins = $hasNew
|
||||
? $pdo->prepare("INSERT INTO pemukiman_miskin_points (kk_nama, nik, jumlah_anggota, latitude, longitude, address, kelurahan, kecamatan, status_bantuan, jenis_bantuan, tanggal_bantuan) VALUES (:kk,:nik,:j,:lat,:lng,:a,:kel,:kec,:s,:jb,:tb)")
|
||||
: $pdo->prepare("INSERT INTO pemukiman_miskin_points (nama, latitude, longitude, address) VALUES (:kk,:lat,:lng,:a)");
|
||||
$n = 0;
|
||||
while (($row = fgetcsv($fp)) !== false) {
|
||||
$kk = $row[$map['kk_nama'] ?? $map['nama'] ?? -1] ?? '';
|
||||
$lat = $row[$map['lat'] ?? $map['latitude'] ?? -1] ?? '';
|
||||
$lng = $row[$map['lng'] ?? $map['longitude'] ?? -1] ?? '';
|
||||
if (trim($kk) === '' || trim($lat) === '' || trim($lng) === '') continue;
|
||||
if ($hasNew) {
|
||||
$ins->execute([
|
||||
':kk' => $kk,
|
||||
':nik' => ($row[$map['nik'] ?? -1] ?? '') ?: null,
|
||||
':j' => (int)($row[$map['jumlah_anggota'] ?? -1] ?? 0),
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':a' => ($row[$map['alamat'] ?? $map['address'] ?? -1] ?? '') ?: null,
|
||||
':kel' => ($row[$map['kelurahan'] ?? -1] ?? '') ?: null,
|
||||
':kec' => ($row[$map['kecamatan'] ?? -1] ?? '') ?: null,
|
||||
':s' => ($row[$map['status_bantuan'] ?? -1] ?? 'Belum dibantu') ?: 'Belum dibantu',
|
||||
':jb' => ($row[$map['jenis_bantuan'] ?? -1] ?? '') ?: null,
|
||||
':tb' => ($row[$map['tanggal_bantuan'] ?? -1] ?? '') ?: null
|
||||
]);
|
||||
} else {
|
||||
$ins->execute([
|
||||
':kk' => $kk,
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':a' => ($row[$map['alamat'] ?? $map['address'] ?? -1] ?? '') ?: null
|
||||
]);
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$pdo->commit();
|
||||
fclose($fp);
|
||||
$msg = "Import selesai: $n baris.";
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
$err = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple edit/create (without anggota details)
|
||||
if (isset($_POST['save_penduduk'])) {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$kk = trim($_POST['kk_nama'] ?? '');
|
||||
$nik = trim($_POST['nik'] ?? '');
|
||||
$jml = (int)($_POST['jumlah_anggota'] ?? 0);
|
||||
$lat = $_POST['latitude'] ?? null;
|
||||
$lng = $_POST['longitude'] ?? null;
|
||||
$addr = trim($_POST['address'] ?? '');
|
||||
$kel = trim($_POST['kelurahan'] ?? '');
|
||||
$kec = trim($_POST['kecamatan'] ?? '');
|
||||
$status = trim($_POST['status_bantuan'] ?? 'Belum dibantu');
|
||||
$jb = trim($_POST['jenis_bantuan'] ?? '');
|
||||
$tb = trim($_POST['tanggal_bantuan'] ?? '');
|
||||
|
||||
if ($kk === '' || $lat === null || $lng === null) $err = 'Data belum lengkap (nama/lat/lng).';
|
||||
else {
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
if (!$hasNew) throw new Exception('Schema belum mendukung fitur penduduk detail. Jalankan schema terbaru.');
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE pemukiman_miskin_points SET kk_nama=:kk, nik=:nik, jumlah_anggota=:j, latitude=:lat, longitude=:lng, address=:a, kelurahan=:kel, kecamatan=:kec, status_bantuan=:s, jenis_bantuan=:jb, tanggal_bantuan=:tb WHERE id=:id");
|
||||
$stmt->execute([':kk'=>$kk,':nik'=>$nik ?: null,':j'=>$jml,':lat'=>$lat,':lng'=>$lng,':a'=>$addr ?: null,':kel'=>$kel ?: null,':kec'=>$kec ?: null,':s'=>$status ?: 'Belum dibantu',':jb'=>$jb ?: null,':tb'=>$tb ?: null,':id'=>$id]);
|
||||
$msg = 'Berhasil diupdate.';
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO pemukiman_miskin_points (kk_nama, nik, jumlah_anggota, latitude, longitude, address, kelurahan, kecamatan, status_bantuan, jenis_bantuan, tanggal_bantuan) VALUES (:kk,:nik,:j,:lat,:lng,:a,:kel,:kec,:s,:jb,:tb)");
|
||||
$stmt->execute([':kk'=>$kk,':nik'=>$nik ?: null,':j'=>$jml,':lat'=>$lat,':lng'=>$lng,':a'=>$addr ?: null,':kel'=>$kel ?: null,':kec'=>$kec ?: null,':s'=>$status ?: 'Belum dibantu',':jb'=>$jb ?: null,':tb'=>$tb ?: null]);
|
||||
$msg = 'Berhasil ditambahkan.';
|
||||
}
|
||||
} catch (Exception $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$id = (int)$_GET['edit'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM pemukiman_miskin_points WHERE id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$edit = $stmt->fetch() ?: null;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
if ($hasNew) {
|
||||
$rows = $pdo->query("
|
||||
SELECT p.*, r.nama AS rumah_ibadah_nama
|
||||
FROM pemukiman_miskin_points p
|
||||
LEFT JOIN rumah_ibadah_points r ON r.id=p.rumah_ibadah_id
|
||||
ORDER BY p.id ASC
|
||||
")->fetchAll();
|
||||
} else {
|
||||
$rows = $pdo->query("SELECT * FROM pemukiman_miskin_points ORDER BY id ASC")->fetchAll();
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
render_header('Admin Penduduk Miskin');
|
||||
?>
|
||||
<div class="admin-card">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
||||
<div style="font-weight:900;font-size:18px"><?= $edit ? 'Edit' : 'Tambah' ?> Penduduk Miskin</div>
|
||||
<div class="actions">
|
||||
<a class="secondary" href="penduduk_miskin.php">Reset</a>
|
||||
<a href="penduduk_miskin.php?export=1">Export CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($msg): ?><div class="notice" style="margin-top:12px;border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="margin-top:12px;border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<form method="post" style="margin-top:12px">
|
||||
<input type="hidden" name="id" value="<?= h($edit['id'] ?? 0) ?>">
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label>Nama Kepala Keluarga</label>
|
||||
<input name="kk_nama" required value="<?= h($edit['kk_nama'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>NIK (opsional)</label>
|
||||
<input name="nik" value="<?= h($edit['nik'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Jumlah Anggota</label>
|
||||
<input name="jumlah_anggota" type="number" min="0" step="1" value="<?= h($edit['jumlah_anggota'] ?? 0) ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Status Bantuan</label>
|
||||
<select name="status_bantuan">
|
||||
<?php foreach (['Belum dibantu','Menunggu verifikasi','Sudah dibantu'] as $s): ?>
|
||||
<option value="<?= h($s) ?>" <?= (($edit['status_bantuan'] ?? 'Belum dibantu') === $s ? 'selected' : '') ?>><?= h($s) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Jenis Bantuan</label>
|
||||
<input name="jenis_bantuan" value="<?= h($edit['jenis_bantuan'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Tanggal Bantuan</label>
|
||||
<input name="tanggal_bantuan" type="date" value="<?= h($edit['tanggal_bantuan'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label style="margin-top:10px;display:block;">Alamat</label>
|
||||
<input name="address" value="<?= h($edit['address'] ?? '') ?>">
|
||||
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Kelurahan</label>
|
||||
<input name="kelurahan" value="<?= h($edit['kelurahan'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Kecamatan</label>
|
||||
<input name="kecamatan" value="<?= h($edit['kecamatan'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Latitude</label>
|
||||
<input name="latitude" required value="<?= h($edit['latitude'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Longitude</label>
|
||||
<input name="longitude" required value="<?= h($edit['longitude'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions" style="margin-top:12px">
|
||||
<button class="btn btn-primary" name="save_penduduk" value="1" type="submit"><?= $edit ? 'Update' : 'Tambah' ?></button>
|
||||
<a class="btn btn-secondary" href="../index.html" style="text-align:center;line-height:38px;text-decoration:none">Kembali ke WebGIS</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top:14px">
|
||||
<div style="font-weight:900;font-size:18px;margin-bottom:8px">Import CSV</div>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="csv_file" accept=".csv" required>
|
||||
<button class="btn btn-secondary" name="import_csv" value="1" type="submit" style="margin-top:10px">Import</button>
|
||||
<div style="margin-top:8px;color:#475569;font-weight:800">Kolom minimal: `kk_nama`/`nama`, `lat`, `lng`.</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top:14px">
|
||||
<div style="font-weight:900;font-size:18px;margin-bottom:8px">Daftar Penduduk Miskin</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th><th>Kepala Keluarga</th><th>Status</th><th>Rumah Ibadah</th><th>Koordinat</th><th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td><?= (int)$r['id'] ?></td>
|
||||
<td><?= h($r['kk_nama'] ?? $r['nama'] ?? '') ?></td>
|
||||
<td><span class="tag"><?= h($r['status_bantuan'] ?? '-') ?></span></td>
|
||||
<td><?= h($r['rumah_ibadah_nama'] ?? '-') ?></td>
|
||||
<td><?= h(($r['latitude'] ?? '') . ', ' . ($r['longitude'] ?? '')) ?></td>
|
||||
<td class="actions">
|
||||
<a class="secondary" href="penduduk_miskin.php?edit=<?= (int)$r['id'] ?>">Edit</a>
|
||||
<a href="penduduk_miskin.php?delete=<?= (int)$r['id'] ?>" onclick="return confirm('Hapus data ini?')">Hapus</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/_layout.php';
|
||||
require_login();
|
||||
|
||||
$msg = '';
|
||||
$err = '';
|
||||
|
||||
// Delete
|
||||
if (isset($_GET['delete'])) {
|
||||
$id = (int)$_GET['delete'];
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM rumah_ibadah_points WHERE id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$msg = 'Berhasil dihapus.';
|
||||
} catch (Exception $e) {
|
||||
$err = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Save (create/update)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$nama = trim($_POST['nama'] ?? '');
|
||||
$jenis = trim($_POST['jenis'] ?? 'Lainnya');
|
||||
$kontak = trim($_POST['kontak'] ?? '');
|
||||
$radius = (float)($_POST['radius_meter'] ?? 300);
|
||||
$lat = $_POST['latitude'] ?? null;
|
||||
$lng = $_POST['longitude'] ?? null;
|
||||
$address = trim($_POST['address'] ?? '');
|
||||
|
||||
if ($nama === '' || $radius <= 0 || $lat === null || $lng === null) {
|
||||
$err = 'Data belum lengkap (nama/radius/lat/lng).';
|
||||
} else {
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM rumah_ibadah_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasJenis = in_array('jenis', $cols, true);
|
||||
$hasKontak = in_array('kontak', $cols, true);
|
||||
|
||||
if ($id > 0) {
|
||||
if ($hasJenis && $hasKontak) {
|
||||
$stmt = $pdo->prepare("UPDATE rumah_ibadah_points SET nama=:n, jenis=:j, kontak=:k, radius_meter=:r, latitude=:lat, longitude=:lng, address=:a WHERE id=:id");
|
||||
$stmt->execute([':n'=>$nama,':j'=>$jenis ?: 'Lainnya',':k'=>$kontak ?: null,':r'=>$radius,':lat'=>$lat,':lng'=>$lng,':a'=>$address ?: null,':id'=>$id]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE rumah_ibadah_points SET nama=:n, radius_meter=:r, latitude=:lat, longitude=:lng, address=:a WHERE id=:id");
|
||||
$stmt->execute([':n'=>$nama,':r'=>$radius,':lat'=>$lat,':lng'=>$lng,':a'=>$address ?: null,':id'=>$id]);
|
||||
}
|
||||
$msg = 'Berhasil diupdate.';
|
||||
} else {
|
||||
if ($hasJenis && $hasKontak) {
|
||||
$stmt = $pdo->prepare("INSERT INTO rumah_ibadah_points (nama, jenis, kontak, radius_meter, latitude, longitude, address) VALUES (:n,:j,:k,:r,:lat,:lng,:a)");
|
||||
$stmt->execute([':n'=>$nama,':j'=>$jenis ?: 'Lainnya',':k'=>$kontak ?: null,':r'=>$radius,':lat'=>$lat,':lng'=>$lng,':a'=>$address ?: null]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO rumah_ibadah_points (nama, radius_meter, latitude, longitude, address) VALUES (:n,:r,:lat,:lng,:a)");
|
||||
$stmt->execute([':n'=>$nama,':r'=>$radius,':lat'=>$lat,':lng'=>$lng,':a'=>$address ?: null]);
|
||||
}
|
||||
$msg = 'Berhasil ditambahkan.';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$err = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$edit = null;
|
||||
if (isset($_GET['edit'])) {
|
||||
$id = (int)$_GET['edit'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM rumah_ibadah_points WHERE id=:id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$edit = $stmt->fetch() ?: null;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
try {
|
||||
$rows = $pdo->query("
|
||||
SELECT r.*,
|
||||
(SELECT COUNT(*) FROM pemukiman_miskin_points p WHERE p.rumah_ibadah_id=r.id) AS binaan_count
|
||||
FROM rumah_ibadah_points r
|
||||
ORDER BY r.id ASC
|
||||
")->fetchAll();
|
||||
} catch (Exception $e) {}
|
||||
|
||||
render_header('Admin Rumah Ibadah');
|
||||
?>
|
||||
<div class="admin-card">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
||||
<div style="font-weight:900;font-size:18px"><?= $edit ? 'Edit' : 'Tambah' ?> Rumah Ibadah</div>
|
||||
<a class="actions a secondary" href="rumah_ibadah.php" style="background:#e2e8f0;color:#0f172a;border-radius:12px;padding:8px 10px;font-weight:900;text-decoration:none">Reset</a>
|
||||
</div>
|
||||
<?php if ($msg): ?><div class="notice" style="margin-top:12px;border-color:#bbf7d0;background:#f0fdf4;color:#166534"><?= h($msg) ?></div><?php endif; ?>
|
||||
<?php if ($err): ?><div class="notice" style="margin-top:12px;border-color:#fecaca;background:#fef2f2;color:#991b1b"><?= h($err) ?></div><?php endif; ?>
|
||||
|
||||
<form method="post" style="margin-top:12px">
|
||||
<input type="hidden" name="id" value="<?= h($edit['id'] ?? 0) ?>">
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label>Nama</label>
|
||||
<input name="nama" required value="<?= h($edit['nama'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Jenis</label>
|
||||
<select name="jenis">
|
||||
<?php foreach (['Masjid','Musholla','Gereja','Katolik','Pura','Vihara','Klenteng','Lainnya'] as $j): ?>
|
||||
<option value="<?= h($j) ?>" <?= (($edit['jenis'] ?? 'Lainnya') === $j ? 'selected' : '') ?>><?= h($j) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label style="margin-top:10px;display:block;">Kontak</label>
|
||||
<input name="kontak" value="<?= h($edit['kontak'] ?? '') ?>">
|
||||
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Radius (meter)</label>
|
||||
<input name="radius_meter" type="number" min="1" step="1" required value="<?= h($edit['radius_meter'] ?? 300) ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Alamat</label>
|
||||
<input name="address" value="<?= h($edit['address'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2" style="margin-top:10px">
|
||||
<div>
|
||||
<label>Latitude</label>
|
||||
<input name="latitude" required value="<?= h($edit['latitude'] ?? '') ?>">
|
||||
</div>
|
||||
<div>
|
||||
<label>Longitude</label>
|
||||
<input name="longitude" required value="<?= h($edit['longitude'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions" style="margin-top:12px">
|
||||
<button class="btn btn-primary" type="submit"><?= $edit ? 'Update' : 'Tambah' ?></button>
|
||||
<a class="btn btn-secondary" href="../index.html" style="text-align:center;line-height:38px;text-decoration:none">Kembali ke WebGIS</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top:14px">
|
||||
<div style="font-weight:900;font-size:18px;margin-bottom:8px">Daftar Rumah Ibadah</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th><th>Nama</th><th>Jenis</th><th>Radius</th><th>Binaan</th><th>Koordinat</th><th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<tr>
|
||||
<td><?= (int)$r['id'] ?></td>
|
||||
<td><?= h($r['nama'] ?? '') ?></td>
|
||||
<td><span class="tag"><?= h($r['jenis'] ?? 'Lainnya') ?></span></td>
|
||||
<td><?= (int)($r['radius_meter'] ?? 0) ?> m</td>
|
||||
<td><?= (int)($r['binaan_count'] ?? 0) ?></td>
|
||||
<td><?= h(($r['latitude'] ?? '') . ', ' . ($r['longitude'] ?? '')) ?></td>
|
||||
<td class="actions">
|
||||
<a class="secondary" href="rumah_ibadah.php?edit=<?= (int)$r['id'] ?>">Edit</a>
|
||||
<a href="rumah_ibadah.php?delete=<?= (int)$r['id'] ?>" onclick="return confirm('Hapus data ini?')">Hapus</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
|
||||
@@ -0,0 +1,779 @@
|
||||
:root {
|
||||
--bg: #0d1520;
|
||||
--bg-soft: #10182c;
|
||||
--bg-card: #141e2e;
|
||||
--card: #ffffff;
|
||||
--text: #e5e7eb;
|
||||
--text-secondary: #cbd5e1;
|
||||
--muted: #8b94a5;
|
||||
--line: #e2e8f0;
|
||||
--primary: #3b82f6;
|
||||
--primary-dark: #1d4ed8;
|
||||
--primary-light: #60a5fa;
|
||||
--secondary: #6b7280;
|
||||
--danger: #ef4444;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
|
||||
--shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 25px 60px rgba(0, 0, 0, 0.3);
|
||||
--radius: 16px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #0f172a;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
body.crosshair #map {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
box-shadow: inset 0 0 40px rgba(0,0,0,0.2);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar { width: 6px; }
|
||||
.sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 6px; }
|
||||
|
||||
.btn-back {
|
||||
display: block;
|
||||
text-align: center;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-back:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 18px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.brand-badge {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 14px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
font-weight: 900;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.brand h1 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(to right, #60a5fa, #34d399);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.brand p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.mode-info {
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(139, 92, 246, 0.1) 100%);
|
||||
border: 1px solid rgba(147, 197, 253, 0.3);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 22px;
|
||||
font-size: 14px;
|
||||
color: #e0f2fe;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
padding: 12px 10px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
.btn i { font-size: 16px; margin-bottom: 2px; }
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255,255,255,0.03);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: #fff;
|
||||
border-color: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn.is-active {
|
||||
outline: 2px solid rgba(147, 197, 253, 0.6);
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.04) 100%);
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
border-radius: 14px;
|
||||
padding: 16px 18px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
backdrop-filter: blur(8px);
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #fff;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 14px;
|
||||
padding: 16px 18px;
|
||||
margin-bottom: 16px;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.panel h2 {
|
||||
font-size: 14px;
|
||||
margin: 0 0 12px;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.legend-sep {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nasional { background: #ef4444; }
|
||||
.provinsi { background: #3b82f6; }
|
||||
.kabupaten { background: #22c55e; }
|
||||
.spbu24 { background: #22c55e; }
|
||||
.spbuNon { background: #ef4444; }
|
||||
.shm { background: #f59e0b; }
|
||||
.hgb { background: #8b5cf6; }
|
||||
.hgu { background: #14b8a6; }
|
||||
.hp { background: #94a3b8; }
|
||||
.ri { background: #7c3aed; }
|
||||
.pmGreen { background: #16a34a; }
|
||||
.pmRed { background: #dc2626; }
|
||||
|
||||
.help-box {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(139, 92, 246, 0.05) 100%);
|
||||
border: 1px solid rgba(147, 197, 253, 0.3);
|
||||
color: #e0f2fe;
|
||||
padding: 16px 18px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.list::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.list::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.35);
|
||||
border-radius: 999px;
|
||||
border: 2px solid rgba(15, 23, 42, 0.3);
|
||||
}
|
||||
|
||||
.list-item {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 11px 13px;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
transform: translateX(4px);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.list-item.is-active {
|
||||
border-color: rgba(147, 197, 253, 0.55);
|
||||
background: rgba(37, 99, 235, 0.16);
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
.list-meta {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.list-empty {
|
||||
font-size: 13px;
|
||||
color: #cbd5e1;
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px dashed rgba(255, 255, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.spbu-icon {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.spbu-dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
border: 2px solid #ffffff;
|
||||
box-shadow: 0 10px 18px rgba(15, 23, 42, 0.22);
|
||||
}
|
||||
|
||||
.spbu-dot--green { background: #22c55e; }
|
||||
.spbu-dot--red { background: #ef4444; }
|
||||
|
||||
.pm-icon {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pm-hit {
|
||||
width: var(--pm-size, 30px);
|
||||
height: var(--pm-size, 30px);
|
||||
border-radius: 999px;
|
||||
background: #0f172a;
|
||||
border: 2px solid #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 14px 22px rgba(15, 23, 42, 0.22);
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.pm-hit--green {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.pm-hit--red {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.pm-hit:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.pm-svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ri-icon {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.ri-hit {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 10px;
|
||||
background: #7c3aed;
|
||||
border: 2px solid #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 12px 18px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.ri-svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.map-area {
|
||||
position: relative;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.map-toolbar {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(226, 232, 240, 0.9);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.12);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.map-toolbar-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.map-tool {
|
||||
border: 0;
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
padding: 9px 12px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
transition: transform .15s ease, opacity .15s ease;
|
||||
}
|
||||
|
||||
.map-tool:hover {
|
||||
transform: translateY(-1px);
|
||||
opacity: 0.96;
|
||||
}
|
||||
|
||||
.map-tool:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.map-tool.is-active {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1f2a44 100%);
|
||||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.edit-bar {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
right: 14px;
|
||||
z-index: 800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 10px 10px 12px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(226, 232, 240, 0.95);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.14);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.edit-bar-title {
|
||||
font-weight: 900;
|
||||
font-size: 13px;
|
||||
color: #0f172a;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.edit-bar-btn {
|
||||
border: 0;
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
padding: 9px 12px;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 900;
|
||||
font-size: 13px;
|
||||
transition: transform .15s ease, opacity .15s ease;
|
||||
}
|
||||
|
||||
.edit-bar-btn.primary {
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.edit-bar-btn.danger {
|
||||
background: linear-gradient(135deg, var(--danger), #b91c1c);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.edit-bar-btn.is-active {
|
||||
outline: 2px solid rgba(15, 23, 42, 0.22);
|
||||
box-shadow: 0 0 0 5px rgba(239, 68, 68, 0.12), 0 14px 30px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.edit-bar-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
opacity: 0.96;
|
||||
}
|
||||
|
||||
.edit-bar-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.leaflet-control-container .leaflet-draw {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.54);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: 9999;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
width: 100%;
|
||||
max-width: 540px;
|
||||
background: #fff;
|
||||
border-radius: 22px;
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 18px 20px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #fff 100%);
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: 30px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
#featureForm {
|
||||
padding: 18px 20px 20px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-section h4 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 15px;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
border: 1px solid #cbd5e1;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 11px 12px;
|
||||
font: inherit;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
input[readonly] {
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
background: rgba(255,255,255,0.03);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.checkbox-row:hover { background: rgba(255,255,255,0.06); color: #fff; }
|
||||
|
||||
.checkbox-row input {
|
||||
accent-color: var(--primary);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 0 0 14px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.modal-actions .btn {
|
||||
flex: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
margin: 12px 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
border: 0;
|
||||
padding: 7px 10px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup-btn.edit { background: #2563eb; }
|
||||
.popup-btn.spatial { background: #7c3aed; }
|
||||
.popup-btn.delete { background: #dc2626; }
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: calc(320px + 16px);
|
||||
bottom: 16px;
|
||||
z-index: 10000;
|
||||
max-width: min(520px, calc(100vw - 32px));
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.22);
|
||||
border: 1px solid rgba(226, 232, 240, 0.9);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
color: #0f172a;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
cursor: pointer;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.toast.toast-success {
|
||||
border-color: rgba(34, 197, 94, 0.35);
|
||||
}
|
||||
|
||||
.toast.toast-error {
|
||||
border-color: rgba(239, 68, 68, 0.35);
|
||||
}
|
||||
|
||||
.toast.toast-info {
|
||||
border-color: rgba(59, 130, 246, 0.35);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.app-shell {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.stats {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.map-area {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.map-toolbar {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.edit-bar {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.toast {
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const kecamatans = [
|
||||
"Kecamatan Pontianak Barat",
|
||||
"Kecamatan Pontianak Kota",
|
||||
"Kecamatan Pontianak Selatan",
|
||||
"Kecamatan Pontianak Tenggara",
|
||||
"Kecamatan Pontianak Timur",
|
||||
"Kecamatan Pontianak Utara"
|
||||
];
|
||||
|
||||
const featureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: []
|
||||
};
|
||||
|
||||
let completed = 0;
|
||||
|
||||
function fetchGeojson(name) {
|
||||
const url = `https://nominatim.openstreetmap.org/search.php?q=${encodeURIComponent(name)}&polygon_geojson=1&format=jsonv2`;
|
||||
const options = {
|
||||
headers: { 'User-Agent': 'WebGIS-SPBU-App/1.0' }
|
||||
};
|
||||
|
||||
https.get(url, options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
const results = JSON.parse(data);
|
||||
const geomResult = results.find(r => r.geojson && r.category === 'boundary' && r.type === 'administrative');
|
||||
if (geomResult) {
|
||||
featureCollection.features.push({
|
||||
type: "Feature",
|
||||
properties: {
|
||||
Kecamatan: name.replace('Kecamatan ', ''),
|
||||
Name: name
|
||||
},
|
||||
geometry: geomResult.geojson
|
||||
});
|
||||
console.log("Fetched " + name);
|
||||
} else {
|
||||
console.log("Not found for " + name);
|
||||
}
|
||||
completed++;
|
||||
if (completed === kecamatans.length) {
|
||||
fs.writeFileSync('js/pontianak_kecamatan.geojson', JSON.stringify(featureCollection, null, 2));
|
||||
console.log("All done! Saved to js/pontianak_kecamatan.geojson");
|
||||
}
|
||||
});
|
||||
}).on('error', (e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
// Nominatim has a usage policy of 1 request per second. We will stagger the requests.
|
||||
kecamatans.forEach((kec, i) => {
|
||||
setTimeout(() => {
|
||||
fetchGeojson(kec);
|
||||
}, i * 1500); // 1.5 seconds apart
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
const query = `[out:json];
|
||||
area["name"="Pontianak"]->.searchArea;
|
||||
(
|
||||
relation["admin_level"="6"](area.searchArea);
|
||||
);
|
||||
out geom;`;
|
||||
|
||||
const options = {
|
||||
hostname: 'overpass-api.de',
|
||||
path: '/api/interpreter',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
fs.writeFileSync('osm_pontianak.json', data);
|
||||
console.log('Done downloading OSM data.');
|
||||
});
|
||||
});
|
||||
req.write(query);
|
||||
req.end();
|
||||
@@ -0,0 +1,359 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>WebGIS SPBU, Jalan, Parsil, Rumah Ibadah, dan Pemukiman</title>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||
|
||||
<link rel="stylesheet" href="css/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app-shell">
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<aside class="sidebar">
|
||||
<a href="../index.html" class="btn btn-back"><i class="fa-solid fa-arrow-left"></i> Kembali ke Landing Page</a>
|
||||
<div class="brand">
|
||||
<div class="brand-badge" aria-hidden="true"><i class="fa-solid fa-map-location-dot"></i></div>
|
||||
<div>
|
||||
<h1>WebGIS SPBU</h1>
|
||||
<p>Infrastruktur & Pemukiman</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modeInfo" class="mode-info">Mode: Tidak aktif</div>
|
||||
|
||||
<div class="action-grid">
|
||||
<button id="btnModePoint" class="btn btn-primary" type="button" onclick="setMode('point')"><i class="fa-solid fa-gas-pump"></i> Tambah Point</button>
|
||||
<button id="btnModeJalan" class="btn btn-secondary" type="button" onclick="setMode('jalan')"><i class="fa-solid fa-road"></i> Tambah Jalan</button>
|
||||
<button id="btnModeParsil" class="btn btn-secondary" type="button" onclick="setMode('parsil')"><i class="fa-solid fa-draw-polygon"></i> Tambah Parsil</button>
|
||||
<button id="btnModeRumahIbadah" class="btn btn-secondary" type="button" onclick="setMode('rumahIbadah')"><i class="fa-solid fa-mosque"></i> Rumah Ibadah</button>
|
||||
<button id="btnModePemukiman" class="btn btn-secondary" type="button" onclick="setMode('pemukiman')"><i class="fa-solid fa-house-chimney-crack"></i> Pemukiman</button>
|
||||
<button id="btnResetMode" class="btn btn-danger" type="button" onclick="resetMode()"><i class="fa-solid fa-xmark"></i> Batal Mode</button>
|
||||
</div>
|
||||
|
||||
<div class="stats" aria-label="Statistik layer">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countPoints">0</div>
|
||||
<div class="stat-label">Point SPBU</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countJalan">0</div>
|
||||
<div class="stat-label">Jalan</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countParsil">0</div>
|
||||
<div class="stat-label">Parsil</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countRumahIbadah">0</div>
|
||||
<div class="stat-label">Rumah Ibadah</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countPemukiman">0</div>
|
||||
<div class="stat-label">Pemukiman Miskin</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="countSektor">0</div>
|
||||
<div class="stat-label">Sektor Kecamatan</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel layer-panel">
|
||||
<h2><i class="fa-solid fa-layer-group"></i> Layer Peta</h2>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="togglePoints" checked>
|
||||
<span class="chk-label">Point SPBU</span>
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="toggleJalan" checked>
|
||||
<span class="chk-label">Jaringan Jalan</span>
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="toggleParsil" checked>
|
||||
<span class="chk-label">Batas Parsil</span>
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="toggleRumahIbadah" checked>
|
||||
<span class="chk-label">Rumah Ibadah (+ radius)</span>
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="togglePemukiman" checked>
|
||||
<span class="chk-label">Pemukiman Miskin</span>
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="toggleSektor" checked>
|
||||
<span class="chk-label">Sektor Kecamatan</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar SPBU</h2>
|
||||
<div id="pointsList" class="list" aria-label="Daftar point SPBU"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar Jalan</h2>
|
||||
<div id="jalanList" class="list" aria-label="Daftar jalan"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar Parsil</h2>
|
||||
<div id="parsilList" class="list" aria-label="Daftar parsil"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar Rumah Ibadah</h2>
|
||||
<div id="rumahIbadahList" class="list" aria-label="Daftar rumah ibadah"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar Pemukiman Miskin</h2>
|
||||
<div id="pemukimanList" class="list" aria-label="Daftar pemukiman miskin"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Daftar Sektor Kecamatan</h2>
|
||||
<div id="sektorList" class="list" aria-label="Daftar sektor kecamatan"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Legenda</h2>
|
||||
<div class="legend-item"><span class="swatch spbu24"></span> SPBU 24 Jam</div>
|
||||
<div class="legend-item"><span class="swatch spbuNon"></span> SPBU Tidak 24 Jam</div>
|
||||
<div class="legend-item"><span class="swatch nasional"></span> Jalan Nasional</div>
|
||||
<div class="legend-item"><span class="swatch provinsi"></span> Jalan Provinsi</div>
|
||||
<div class="legend-item"><span class="swatch kabupaten"></span> Jalan Kabupaten</div>
|
||||
<div class="legend-item legend-sep"><span class="swatch shm"></span> Parsil SHM</div>
|
||||
<div class="legend-item"><span class="swatch hgb"></span> Parsil HGB</div>
|
||||
<div class="legend-item"><span class="swatch hgu"></span> Parsil HGU</div>
|
||||
<div class="legend-item"><span class="swatch hp"></span> Parsil HP</div>
|
||||
<div class="legend-item legend-sep"><span class="swatch ri"></span> Rumah Ibadah</div>
|
||||
<div class="legend-item"><span class="swatch pmGreen"></span> Pemukiman (dalam radius bantuan)</div>
|
||||
<div class="legend-item"><span class="swatch pmRed"></span> Pemukiman (di luar radius bantuan)</div>
|
||||
</div>
|
||||
|
||||
<div id="helpBox" class="help-box">
|
||||
Pilih mode di atas, lalu:
|
||||
<br><b>Point</b>: klik lokasi di peta.
|
||||
<br><b>Jalan</b>: gambar garis, double-click untuk selesai.
|
||||
<br><b>Parsil</b>: gambar poligon, klik titik awal untuk selesai.
|
||||
<br><b>Rumah Ibadah</b>: klik lokasi di peta.
|
||||
<br><b>Pemukiman</b>: klik lokasi di peta.
|
||||
<br><br>Klik objek di peta untuk <b>Edit</b> / <b>Hapus</b> (CRUD).
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- MAP -->
|
||||
<main class="map-area">
|
||||
<div class="map-toolbar" aria-label="Toolbar peta">
|
||||
<div class="map-toolbar-row">
|
||||
<button class="map-tool" id="btnResetView" type="button">Reset View</button>
|
||||
<button class="map-tool" id="btnMyLocation" type="button">Lokasi Saya</button>
|
||||
</div>
|
||||
<div class="map-toolbar-row">
|
||||
<button class="map-tool" id="btnFilter24" type="button">SPBU 24 Jam</button>
|
||||
<button class="map-tool" id="btnFilterNon24" type="button">SPBU Tidak 24 Jam</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editBar" class="edit-bar hidden" aria-label="Edit lokasi">
|
||||
<div class="edit-bar-title" id="editBarTitle">Edit Lokasi</div>
|
||||
<button class="edit-bar-btn danger hidden" id="btnDeleteVertex" type="button">Hapus Vertex</button>
|
||||
<button class="edit-bar-btn primary" id="btnSaveLocation" type="button">Simpan Lokasi</button>
|
||||
<button class="edit-bar-btn" id="btnCancelLocation" type="button">Batal</button>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div id="featureModal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
||||
<div class="modal-card">
|
||||
|
||||
<div class="modal-header">
|
||||
<h3 id="modalTitle">Input Data</h3>
|
||||
<button type="button" class="close-btn" onclick="closeModal()" aria-label="Tutup">×</button>
|
||||
</div>
|
||||
|
||||
<form id="featureForm">
|
||||
<p id="formNotice" class="notice hidden"></p>
|
||||
|
||||
<!-- POINT -->
|
||||
<div id="pointFields" class="form-section hidden">
|
||||
<h4>Point SPBU</h4>
|
||||
|
||||
<label for="namaPoint">Nama</label>
|
||||
<input id="namaPoint" placeholder="Contoh: SPBU Sudirman" required>
|
||||
|
||||
<label for="noPoint">No</label>
|
||||
<input id="noPoint" placeholder="Contoh: 64.781.01" required>
|
||||
|
||||
<label for="deskripsiPoint">Deskripsi</label>
|
||||
<textarea id="deskripsiPoint" placeholder="Catatan singkat (opsional)" rows="3"></textarea>
|
||||
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" id="status24Jam"> 24 Jam
|
||||
</label>
|
||||
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label for="latPoint">Latitude</label>
|
||||
<input id="latPoint" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lngPoint">Longitude</label>
|
||||
<input id="lngPoint" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JALAN -->
|
||||
<div id="roadFields" class="form-section hidden">
|
||||
<h4>Jalan</h4>
|
||||
|
||||
<label for="namaJalan">Nama Jalan</label>
|
||||
<input id="namaJalan" placeholder="Contoh: Jl. Ahmad Yani" required>
|
||||
|
||||
<label for="statusJalan">Status</label>
|
||||
<select id="statusJalan">
|
||||
<option value="Nasional">Nasional</option>
|
||||
<option value="Provinsi">Provinsi</option>
|
||||
<option value="Kabupaten">Kabupaten</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- PARSIL -->
|
||||
<div id="parsilFields" class="form-section hidden">
|
||||
<h4>Parsil</h4>
|
||||
|
||||
<label for="statusParsil">Status Kepemilikan</label>
|
||||
<select id="statusParsil">
|
||||
<option value="SHM">SHM</option>
|
||||
<option value="HGB">HGB</option>
|
||||
<option value="HGU">HGU</option>
|
||||
<option value="HP">HP</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- RUMAH IBADAH -->
|
||||
<div id="rumahIbadahFields" class="form-section hidden">
|
||||
<h4>Rumah Ibadah</h4>
|
||||
|
||||
<label for="namaRumahIbadah">Nama</label>
|
||||
<input id="namaRumahIbadah" placeholder="Contoh: Masjid Al-Ikhlas" required>
|
||||
|
||||
<label for="jenisRumahIbadah">Jenis</label>
|
||||
<select id="jenisRumahIbadah">
|
||||
<option value="Lainnya">Lainnya</option>
|
||||
<option value="Masjid">Masjid</option>
|
||||
<option value="Gereja">Gereja</option>
|
||||
<option value="Pura">Pura</option>
|
||||
<option value="Vihara">Vihara</option>
|
||||
<option value="Klenteng">Klenteng</option>
|
||||
</select>
|
||||
|
||||
<label for="kontakRumahIbadah">Kontak (opsional)</label>
|
||||
<input id="kontakRumahIbadah" placeholder="No HP / PIC (opsional)">
|
||||
|
||||
<label for="radiusRumahIbadah">Radius Bantuan (meter)</label>
|
||||
<input id="radiusRumahIbadah" type="number" min="1" step="1" placeholder="Contoh: 500" required>
|
||||
|
||||
<label for="alamatRumahIbadah">Alamat (opsional)</label>
|
||||
<textarea id="alamatRumahIbadah" placeholder="Alamat singkat (opsional)" rows="2"></textarea>
|
||||
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label for="latRumahIbadah">Latitude</label>
|
||||
<input id="latRumahIbadah" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lngRumahIbadah">Longitude</label>
|
||||
<input id="lngRumahIbadah" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PEMUKIMAN MISKIN -->
|
||||
<div id="pemukimanFields" class="form-section hidden">
|
||||
<h4>Pemukiman Miskin</h4>
|
||||
|
||||
<label for="kkNamaPemukiman">Nama KK</label>
|
||||
<input id="kkNamaPemukiman" placeholder="Contoh: Budi" required>
|
||||
|
||||
<label for="nikPemukiman">NIK (opsional)</label>
|
||||
<input id="nikPemukiman" placeholder="Contoh: 6171xxxxxxxxxxxx">
|
||||
|
||||
<label for="jumlahAnggotaPemukiman">Jumlah Anggota (opsional)</label>
|
||||
<input id="jumlahAnggotaPemukiman" type="number" min="0" step="1" placeholder="Contoh: 4">
|
||||
|
||||
<label for="statusBantuanPemukiman">Status Bantuan</label>
|
||||
<select id="statusBantuanPemukiman">
|
||||
<option value="Belum dibantu">Belum dibantu</option>
|
||||
<option value="Menunggu verifikasi">Menunggu verifikasi</option>
|
||||
<option value="Sudah dibantu">Sudah dibantu</option>
|
||||
</select>
|
||||
|
||||
<label for="jenisBantuanPemukiman">Jenis Bantuan (opsional)</label>
|
||||
<input id="jenisBantuanPemukiman" placeholder="Contoh: Sembako">
|
||||
|
||||
<label for="tanggalBantuanPemukiman">Tanggal Bantuan (opsional)</label>
|
||||
<input id="tanggalBantuanPemukiman" type="date">
|
||||
|
||||
<label for="alamatPemukiman">Alamat (opsional)</label>
|
||||
<textarea id="alamatPemukiman" placeholder="Alamat singkat (opsional)" rows="2"></textarea>
|
||||
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label for="kelurahanPemukiman">Kelurahan (opsional)</label>
|
||||
<input id="kelurahanPemukiman" placeholder="Contoh: Sungai Jawi">
|
||||
</div>
|
||||
<div>
|
||||
<label for="kecamatanPemukiman">Kecamatan (opsional)</label>
|
||||
<input id="kecamatanPemukiman" placeholder="Contoh: Pontianak Kota">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<label for="latPemukiman">Latitude</label>
|
||||
<input id="latPemukiman" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="lngPemukiman">Longitude</label>
|
||||
<input id="lngPemukiman" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BUTTON -->
|
||||
<div class="modal-actions">
|
||||
<button id="btnSubmitFeature" class="btn btn-primary" type="submit">Simpan Data</button>
|
||||
<button class="btn btn-secondary" type="button" onclick="closeModal()">Batal</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOAST -->
|
||||
<div id="toast" class="toast hidden" aria-live="polite" aria-atomic="true"></div>
|
||||
|
||||
<!-- SCRIPT -->
|
||||
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.geometryutil/0.10.1/leaflet.geometryutil.min.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
require_once 'php/koneksi.php';
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE rumah_ibadah_points ADD COLUMN IF NOT EXISTS jenis VARCHAR(30) NOT NULL DEFAULT 'Lainnya' AFTER nama");
|
||||
$pdo->exec("ALTER TABLE rumah_ibadah_points ADD COLUMN IF NOT EXISTS kontak VARCHAR(120) NULL AFTER jenis");
|
||||
echo "Columns added successfully.\n";
|
||||
} catch (PDOException $e) {
|
||||
echo "Failed to add columns: " . $e->getMessage() . "\n";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html><head>
|
||||
<title>406 Not Acceptable</title>
|
||||
</head><body>
|
||||
<h1>Not Acceptable</h1>
|
||||
<p>An appropriate representation of the requested resource could not be found on this server.</p>
|
||||
<hr>
|
||||
<address>Apache/2.4.67 (Debian) Server at overpass-api.de Port 443</address>
|
||||
</body></html>
|
||||
@@ -0,0 +1,40 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const data = JSON.parse(fs.readFileSync('osm_pontianak.json', 'utf8'));
|
||||
|
||||
const geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
};
|
||||
|
||||
data.elements.forEach(el => {
|
||||
if (el.type === 'relation' && el.tags && el.tags.admin_level === '6') {
|
||||
const name = el.tags.name;
|
||||
// Extract polygons from relation members
|
||||
const coordinates = [];
|
||||
el.members.forEach(member => {
|
||||
if (member.type === 'way' && member.geometry) {
|
||||
const wayCoords = member.geometry.map(pt => [pt.lon, pt.lat]);
|
||||
coordinates.push(wayCoords);
|
||||
}
|
||||
});
|
||||
|
||||
// Assemble into MultiPolygon or Polygon (simplistic)
|
||||
if (coordinates.length > 0) {
|
||||
geojson.features.push({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
kecamatan: name,
|
||||
...el.tags
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: coordinates
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Since OSM returns fragments of outer and inner rings in relations, a naive conversion might have topological issues in Leaflet (like crossing lines if not ordered).
|
||||
// We should use an existing library like osmtogeojson. Let's install it and use it.
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(["success"=>false,"message"=>"Data kosong"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$geojson = json_encode($data['geometry'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO jalan_lines
|
||||
(nama_jalan, status_jalan, panjang_meter, geojson)
|
||||
VALUES (?, ?, ?, ?)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$data['nama_jalan'],
|
||||
$data['status_jalan'],
|
||||
$data['panjang_meter'],
|
||||
$geojson
|
||||
]);
|
||||
} catch (Exception $inner) {
|
||||
// Fallback untuk skema spatial: simpan ke kolom `geom` jika `geojson` tidak ada.
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO jalan_lines
|
||||
(nama_jalan, status_jalan, panjang_meter, geom)
|
||||
VALUES (?, ?, ?, ST_GeomFromGeoJSON(?))
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$data['nama_jalan'],
|
||||
$data['status_jalan'],
|
||||
$data['panjang_meter'],
|
||||
$geojson
|
||||
]);
|
||||
}
|
||||
|
||||
echo json_encode(["success"=>true]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success"=>false,"message"=>$e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
// Payload expected from js/app.js:
|
||||
// { status_kepemilikan, luas_m2, geometry }
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => "Payload JSON kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$status_kepemilikan = trim($data['status_kepemilikan'] ?? '');
|
||||
$luas_m2 = (float)($data['luas_m2'] ?? 0);
|
||||
$geometry = $data['geometry'] ?? null;
|
||||
|
||||
if ($status_kepemilikan === '' || !$geometry) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => "Data parsil belum lengkap (status/geometry)."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$geomJson = json_encode($geometry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO parsil_tanah (status_kepemilikan, luas_m2, geojson)
|
||||
VALUES (:status_kepemilikan, :luas_m2, :geojson)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':status_kepemilikan' => $status_kepemilikan,
|
||||
':luas_m2' => $luas_m2,
|
||||
':geojson' => $geomJson
|
||||
]);
|
||||
} catch (Exception $inner) {
|
||||
// Fallback untuk skema spatial: simpan ke kolom `geom` jika `geojson` tidak ada.
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO parsil_tanah (status_kepemilikan, luas_m2, geom)
|
||||
VALUES (:status_kepemilikan, :luas_m2, ST_GeomFromGeoJSON(:geom))
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':status_kepemilikan' => $status_kepemilikan,
|
||||
':luas_m2' => $luas_m2,
|
||||
':geom' => $geomJson
|
||||
]);
|
||||
} catch (Exception $inner2) {
|
||||
// Last resort: minimal insert.
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO parsil_tanah (geom)
|
||||
VALUES (ST_GeomFromGeoJSON(:geom))
|
||||
");
|
||||
$stmt->execute([':geom' => $geomJson]);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "status" => "success", "message" => "Area Parsil berhasil disimpan."]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
function haversine_m($lat1, $lon1, $lat2, $lon2) {
|
||||
$R = 6371000.0; // meters
|
||||
$phi1 = deg2rad((float)$lat1);
|
||||
$phi2 = deg2rad((float)$lat2);
|
||||
$dphi = deg2rad((float)$lat2 - (float)$lat1);
|
||||
$dlambda = deg2rad((float)$lon2 - (float)$lon1);
|
||||
$a = sin($dphi/2) * sin($dphi/2) + cos($phi1) * cos($phi2) * sin($dlambda/2) * sin($dlambda/2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
return $R * $c;
|
||||
}
|
||||
|
||||
function get_payload() {
|
||||
$ct = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
if (stripos($ct, 'multipart/form-data') !== false) {
|
||||
return [
|
||||
'kk_nama' => $_POST['kk_nama'] ?? '',
|
||||
'nik' => $_POST['nik'] ?? '',
|
||||
'jumlah_anggota' => $_POST['jumlah_anggota'] ?? '',
|
||||
'latitude' => $_POST['latitude'] ?? null,
|
||||
'longitude' => $_POST['longitude'] ?? null,
|
||||
'address' => $_POST['address'] ?? '',
|
||||
'kelurahan' => $_POST['kelurahan'] ?? '',
|
||||
'kecamatan' => $_POST['kecamatan'] ?? '',
|
||||
'status_bantuan' => $_POST['status_bantuan'] ?? 'Belum dibantu',
|
||||
'jenis_bantuan' => $_POST['jenis_bantuan'] ?? '',
|
||||
'tanggal_bantuan' => $_POST['tanggal_bantuan'] ?? '',
|
||||
'anggota' => $_POST['anggota'] ?? '[]'
|
||||
];
|
||||
}
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
return is_array($json) ? $json : null;
|
||||
}
|
||||
|
||||
function compute_nearest_ibadah(PDO $pdo, $lat, $lng) {
|
||||
$stmt = $pdo->query("SELECT id, latitude, longitude, radius_meter FROM rumah_ibadah_points");
|
||||
$rows = $stmt->fetchAll();
|
||||
$best = null;
|
||||
foreach ($rows as $r) {
|
||||
$rLat = (float)$r['latitude'];
|
||||
$rLng = (float)$r['longitude'];
|
||||
$radius = (float)$r['radius_meter'];
|
||||
if ($radius <= 0) continue;
|
||||
$d = haversine_m($lat, $lng, $rLat, $rLng);
|
||||
if ($d <= $radius) {
|
||||
if ($best === null || $d < $best['jarak_meter']) {
|
||||
$best = ['rumah_ibadah_id' => (int)$r['id'], 'jarak_meter' => $d];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best ?: ['rumah_ibadah_id' => null, 'jarak_meter' => null];
|
||||
}
|
||||
|
||||
function save_upload_if_any() {
|
||||
if (!isset($_FILES['bukti_file']) || !is_array($_FILES['bukti_file'])) return null;
|
||||
if (($_FILES['bukti_file']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return null;
|
||||
|
||||
$dir = __DIR__ . '/../uploads/bukti';
|
||||
if (!is_dir($dir)) @mkdir($dir, 0775, true);
|
||||
if (!is_dir($dir)) return null;
|
||||
|
||||
$orig = (string)($_FILES['bukti_file']['name'] ?? 'file');
|
||||
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
|
||||
$allowed = ['jpg','jpeg','png','webp','pdf'];
|
||||
if ($ext && !in_array($ext, $allowed, true)) return null;
|
||||
|
||||
$safeBase = preg_replace('/[^a-zA-Z0-9._-]+/', '_', pathinfo($orig, PATHINFO_FILENAME));
|
||||
$safeBase = trim($safeBase, '_');
|
||||
if ($safeBase === '') $safeBase = 'bukti';
|
||||
$filename = $safeBase . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . ($ext ? ('.' . $ext) : '');
|
||||
$dest = $dir . '/' . $filename;
|
||||
|
||||
if (!move_uploaded_file($_FILES['bukti_file']['tmp_name'], $dest)) return null;
|
||||
return 'uploads/bukti/' . $filename;
|
||||
}
|
||||
|
||||
$data = get_payload();
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Payload kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$kk_nama = trim($data['kk_nama'] ?? $data['nama'] ?? '');
|
||||
$nik = trim($data['nik'] ?? '');
|
||||
$jumlah_anggota = (int)($data['jumlah_anggota'] ?? 0);
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
$address = trim($data['address'] ?? '');
|
||||
$kelurahan = trim($data['kelurahan'] ?? '');
|
||||
$kecamatan = trim($data['kecamatan'] ?? '');
|
||||
$status_bantuan = trim($data['status_bantuan'] ?? 'Belum dibantu');
|
||||
$jenis_bantuan = trim($data['jenis_bantuan'] ?? '');
|
||||
$tanggal_bantuan = trim($data['tanggal_bantuan'] ?? '');
|
||||
$anggotaRaw = $data['anggota'] ?? [];
|
||||
|
||||
$validStatus = ['Belum dibantu', 'Sudah dibantu', 'Menunggu verifikasi'];
|
||||
if (!in_array($status_bantuan, $validStatus, true)) $status_bantuan = 'Belum dibantu';
|
||||
|
||||
if ($kk_nama === '' || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data penduduk miskin belum lengkap (nama/lat/lng)."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$lat = (float)$latitude;
|
||||
$lng = (float)$longitude;
|
||||
$nearest = compute_nearest_ibadah($pdo, $lat, $lng);
|
||||
$uploadPath = save_upload_if_any();
|
||||
|
||||
$pdo->beginTransaction();
|
||||
// Backward-compat: some installs still have old column `nama`.
|
||||
// Prefer new schema if columns exist.
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
if ($hasNew) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO pemukiman_miskin_points
|
||||
(kk_nama, nik, jumlah_anggota, latitude, longitude, address, kelurahan, kecamatan,
|
||||
status_bantuan, jenis_bantuan, tanggal_bantuan, bukti_file, jarak_meter, rumah_ibadah_id)
|
||||
VALUES
|
||||
(:kk_nama, :nik, :jumlah_anggota, :latitude, :longitude, :address, :kelurahan, :kecamatan,
|
||||
:status_bantuan, :jenis_bantuan, :tanggal_bantuan, :bukti_file, :jarak_meter, :rumah_ibadah_id)
|
||||
");
|
||||
$stmt->execute([
|
||||
':kk_nama' => $kk_nama,
|
||||
':nik' => ($nik === '' ? null : $nik),
|
||||
':jumlah_anggota' => $jumlah_anggota,
|
||||
':latitude' => $lat,
|
||||
':longitude' => $lng,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':kelurahan' => ($kelurahan === '' ? null : $kelurahan),
|
||||
':kecamatan' => ($kecamatan === '' ? null : $kecamatan),
|
||||
':status_bantuan' => $status_bantuan,
|
||||
':jenis_bantuan' => ($jenis_bantuan === '' ? null : $jenis_bantuan),
|
||||
':tanggal_bantuan' => ($tanggal_bantuan === '' ? null : $tanggal_bantuan),
|
||||
':bukti_file' => $uploadPath,
|
||||
':jarak_meter' => $nearest['jarak_meter'],
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id']
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO pemukiman_miskin_points (nama, latitude, longitude, address, rumah_ibadah_id)
|
||||
VALUES (:nama, :latitude, :longitude, :address, :rumah_ibadah_id)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $kk_nama,
|
||||
':latitude' => $lat,
|
||||
':longitude' => $lng,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id']
|
||||
]);
|
||||
}
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
// anggota keluarga (optional)
|
||||
$anggota = is_string($anggotaRaw) ? json_decode($anggotaRaw, true) : $anggotaRaw;
|
||||
if (is_array($anggota) && count($anggota) > 0 && $hasNew) {
|
||||
$ins = $pdo->prepare("
|
||||
INSERT INTO anggota_keluarga (penduduk_id, nama, hubungan, umur, pekerjaan, keterangan)
|
||||
VALUES (:penduduk_id, :nama, :hubungan, :umur, :pekerjaan, :keterangan)
|
||||
");
|
||||
foreach ($anggota as $a) {
|
||||
$n = trim((string)($a['nama'] ?? ''));
|
||||
$h = trim((string)($a['hubungan'] ?? ''));
|
||||
if ($n === '' || $h === '') continue;
|
||||
$ins->execute([
|
||||
':penduduk_id' => $newId,
|
||||
':nama' => $n,
|
||||
':hubungan' => $h,
|
||||
':umur' => isset($a['umur']) && $a['umur'] !== '' ? (int)$a['umur'] : null,
|
||||
':pekerjaan' => ($a['pekerjaan'] ?? '') === '' ? null : (string)$a['pekerjaan'],
|
||||
':keterangan' => ($a['keterangan'] ?? '') === '' ? null : (string)$a['keterangan']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"id" => $newId,
|
||||
"rumah_ibadah_id" => $nearest['rumah_ibadah_id'],
|
||||
"jarak_meter" => $nearest['jarak_meter'],
|
||||
"message" => "Penduduk miskin berhasil ditambahkan"
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => "Payload JSON kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$nama = trim($data['nama'] ?? '');
|
||||
$no = trim($data['no'] ?? '');
|
||||
$deskripsi = trim($data['deskripsi'] ?? '');
|
||||
$status_24jam = isset($data['status_24jam']) ? (int)$data['status_24jam'] : 0;
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
|
||||
if ($nama === '' || $no === '' || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => "Data point belum lengkap (nama/no/lat/lng)."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pointGeojson = json_encode([
|
||||
"type" => "Point",
|
||||
"coordinates" => [(float)$longitude, (float)$latitude]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
// Optional legacy field if client ever sends it
|
||||
$geomPayload = $data['geom'] ?? null;
|
||||
if (!is_string($geomPayload) || $geomPayload === '') {
|
||||
$geomPayload = $pointGeojson;
|
||||
}
|
||||
|
||||
try {
|
||||
// Preferred schema (non-spatial, consistent with update_point.php)
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO spbu_points (nama, no, deskripsi, status_24jam, latitude, longitude)
|
||||
VALUES (:nama, :no, :deskripsi, :status_24jam, :latitude, :longitude)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':no' => $no,
|
||||
':deskripsi' => $deskripsi,
|
||||
':status_24jam' => $status_24jam,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"status" => "success",
|
||||
"message" => "Point SPBU berhasil ditambahkan",
|
||||
"id" => (int)$pdo->lastInsertId()
|
||||
]);
|
||||
exit;
|
||||
} catch (Exception $e1) {
|
||||
// continue to fallback attempts
|
||||
}
|
||||
|
||||
// Fallback schema: `geom` stored as LONGTEXT (GeoJSON string)
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO spbu_points (nama, no, deskripsi, status_24jam, latitude, longitude, geom)
|
||||
VALUES (:nama, :no, :deskripsi, :status_24jam, :latitude, :longitude, :geom)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':no' => $no,
|
||||
':deskripsi' => $deskripsi,
|
||||
':status_24jam' => $status_24jam,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':geom' => $geomPayload
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"status" => "success",
|
||||
"message" => "Point SPBU berhasil ditambahkan",
|
||||
"id" => (int)$pdo->lastInsertId()
|
||||
]);
|
||||
exit;
|
||||
} catch (Exception $e2) {
|
||||
// continue to fallback attempts
|
||||
}
|
||||
|
||||
// Fallback schema: only (nama, geom) where geom is LONGTEXT GeoJSON
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO spbu_points (nama, geom) VALUES (:nama, :geom)");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':geom' => $geomPayload
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"status" => "success",
|
||||
"message" => "Point SPBU berhasil ditambahkan",
|
||||
"id" => (int)$pdo->lastInsertId()
|
||||
]);
|
||||
exit;
|
||||
} catch (Exception $e3) {
|
||||
// continue to fallback attempts
|
||||
}
|
||||
|
||||
// Fallback schema: `geom` is spatial GEOMETRY/POINT
|
||||
$stmt = $pdo->prepare("INSERT INTO spbu_points (nama, geom) VALUES (:nama, ST_GeomFromGeoJSON(:geom))");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':geom' => $pointGeojson
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"status" => "success",
|
||||
"message" => "Point SPBU berhasil ditambahkan",
|
||||
"id" => (int)$pdo->lastInsertId()
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "status" => "error", "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Payload JSON kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$nama = trim($data['nama'] ?? '');
|
||||
$jenis = trim($data['jenis'] ?? 'Lainnya');
|
||||
$kontak = trim($data['kontak'] ?? '');
|
||||
$radius_meter = (float)($data['radius_meter'] ?? 0);
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
$address = trim($data['address'] ?? '');
|
||||
|
||||
if ($nama === '' || $radius_meter <= 0 || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data rumah ibadah belum lengkap (nama/radius/lat/lng)."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM rumah_ibadah_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasJenis = in_array('jenis', $cols, true);
|
||||
$hasKontak = in_array('kontak', $cols, true);
|
||||
|
||||
if ($hasJenis && $hasKontak) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO rumah_ibadah_points (nama, jenis, kontak, radius_meter, latitude, longitude, address)
|
||||
VALUES (:nama, :jenis, :kontak, :radius_meter, :latitude, :longitude, :address)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':jenis' => ($jenis === '' ? 'Lainnya' : $jenis),
|
||||
':kontak' => ($kontak === '' ? null : $kontak),
|
||||
':radius_meter' => $radius_meter,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':address' => ($address === '' ? null : $address)
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO rumah_ibadah_points (nama, radius_meter, latitude, longitude, address)
|
||||
VALUES (:nama, :radius_meter, :latitude, :longitude, :address)
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':radius_meter' => $radius_meter,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':address' => ($address === '' ? null : $address)
|
||||
]);
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "id" => (int)$pdo->lastInsertId(), "message" => "Rumah ibadah berhasil ditambahkan"]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "ID tidak valid"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM jalan_lines WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data jalan berhasil dihapus"]);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "ID tidak valid"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM parsil_tanah WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data parsil berhasil dihapus"]);
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "ID tidak valid"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// best-effort delete uploaded proof file
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
if (in_array('bukti_file', $cols, true)) {
|
||||
$stmt = $pdo->prepare("SELECT bukti_file FROM pemukiman_miskin_points WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$path = $stmt->fetchColumn();
|
||||
if ($path && is_string($path) && strpos($path, 'uploads/bukti/') === 0) {
|
||||
$full = __DIR__ . '/../' . $path;
|
||||
if (is_file($full)) @unlink($full);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) { /* ignore */ }
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM pemukiman_miskin_points WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(["success" => true, "message" => "Pemukiman miskin berhasil dihapus"]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "ID tidak valid"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM spbu_points WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data point berhasil dihapus"]);
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "ID tidak valid"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM rumah_ibadah_points WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(["success" => true, "message" => "Rumah ibadah berhasil dihapus"]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
$host = getenv('DB_HOST') ?: "localhost";
|
||||
$port = getenv('DB_PORT') ?: "3306";
|
||||
$db = getenv('DB_NAME') ?: "webgis_spbu";
|
||||
$user = getenv('DB_USER') ?: "root";
|
||||
$pass = getenv('DB_PASSWORD') ?: "";
|
||||
$charset = "utf8mb4";
|
||||
|
||||
$dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
"success" => false,
|
||||
"message" => "Koneksi database gagal: " . $e->getMessage()
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$penduduk_id = isset($_GET['penduduk_id']) ? (int)$_GET['penduduk_id'] : 0;
|
||||
if ($penduduk_id <= 0) {
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, penduduk_id, nama, hubungan, umur, pekerjaan, keterangan
|
||||
FROM anggota_keluarga
|
||||
WHERE penduduk_id = :id
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$stmt->execute([':id' => $penduduk_id]);
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$features = [];
|
||||
|
||||
function rowsToFeatures($stmt) {
|
||||
$features = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$geometry = json_decode($row['geojson'] ?? '', true);
|
||||
if (!$geometry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
"type" => "Feature",
|
||||
"geometry" => $geometry,
|
||||
"properties" => [
|
||||
"id" => (int)($row['id'] ?? 0),
|
||||
"nama_jalan" => $row['nama_jalan'] ?? null,
|
||||
"status_jalan" => $row['status_jalan'] ?? null,
|
||||
"panjang_meter" => isset($row['panjang_meter']) ? (float)$row['panjang_meter'] : null
|
||||
]
|
||||
];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
try {
|
||||
// Preferred schema: geojson column.
|
||||
$stmt = $pdo->query("SELECT id, nama_jalan, status_jalan, panjang_meter, geojson FROM jalan_lines ORDER BY id ASC");
|
||||
$features = rowsToFeatures($stmt);
|
||||
} catch (Exception $e) {
|
||||
// Fallback schema: spatial geom.
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
id,
|
||||
nama_jalan,
|
||||
status_jalan,
|
||||
panjang_meter,
|
||||
ST_AsGeoJSON(geom) AS geojson
|
||||
FROM jalan_lines
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$features = rowsToFeatures($stmt);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"type" => "FeatureCollection",
|
||||
"features" => $features
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$features = [];
|
||||
|
||||
function rowsToFeatures($stmt) {
|
||||
$features = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$geometry = json_decode($row['geojson'] ?? '', true);
|
||||
if (!$geometry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$features[] = [
|
||||
"type" => "Feature",
|
||||
"geometry" => $geometry,
|
||||
"properties" => [
|
||||
"id" => (int)($row['id'] ?? 0),
|
||||
"status_kepemilikan" => $row['status_kepemilikan'] ?? null,
|
||||
"luas_m2" => isset($row['luas_m2']) ? (float)$row['luas_m2'] : null
|
||||
]
|
||||
];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
try {
|
||||
// Preferred schema: geojson column.
|
||||
$stmt = $pdo->query("SELECT id, status_kepemilikan, luas_m2, geojson FROM parsil_tanah ORDER BY id ASC");
|
||||
$features = rowsToFeatures($stmt);
|
||||
} catch (Exception $e) {
|
||||
// Fallback schema: spatial geom.
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
id,
|
||||
status_kepemilikan,
|
||||
luas_m2,
|
||||
ST_AsGeoJSON(geom) AS geojson
|
||||
FROM parsil_tanah
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$features = rowsToFeatures($stmt);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"type" => "FeatureCollection",
|
||||
"features" => $features
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
function haversine_m($lat1, $lon1, $lat2, $lon2) {
|
||||
$R = 6371000.0;
|
||||
$phi1 = deg2rad((float)$lat1);
|
||||
$phi2 = deg2rad((float)$lat2);
|
||||
$dphi = deg2rad((float)$lat2 - (float)$lat1);
|
||||
$dlambda = deg2rad((float)$lon2 - (float)$lon1);
|
||||
$a = sin($dphi/2) * sin($dphi/2) + cos($phi1) * cos($phi2) * sin($dlambda/2) * sin($dlambda/2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
return $R * $c;
|
||||
}
|
||||
|
||||
function compute_nearest_ibadah(array $ibadahRows, $lat, $lng) {
|
||||
$best = null;
|
||||
foreach ($ibadahRows as $r) {
|
||||
$rLat = (float)($r['latitude'] ?? 0);
|
||||
$rLng = (float)($r['longitude'] ?? 0);
|
||||
$radius = (float)($r['radius_meter'] ?? 0);
|
||||
if ($radius <= 0) continue;
|
||||
$d = haversine_m($lat, $lng, $rLat, $rLng);
|
||||
if ($d <= $radius) {
|
||||
if ($best === null || $d < $best['jarak_meter']) {
|
||||
$best = [
|
||||
'rumah_ibadah_id' => (int)($r['id'] ?? 0),
|
||||
'jarak_meter' => $d,
|
||||
'row' => $r
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best;
|
||||
}
|
||||
|
||||
// Prefer new schema; fall back to old schema if needed.
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
|
||||
$hasJarak = in_array('jarak_meter', $cols, true);
|
||||
$hasRumahIbadahId = in_array('rumah_ibadah_id', $cols, true);
|
||||
|
||||
// Fetch rumah ibadah once; used for real-time coverage computation server-side.
|
||||
$ibadah = [];
|
||||
try {
|
||||
$stmtI = $pdo->query("SELECT id, nama, jenis, kontak, radius_meter, latitude, longitude FROM rumah_ibadah_points");
|
||||
$ibadah = $stmtI->fetchAll();
|
||||
} catch (Exception $e) {
|
||||
$ibadah = [];
|
||||
}
|
||||
|
||||
// Load pemukiman rows
|
||||
$rows = [];
|
||||
try {
|
||||
if ($hasNew) {
|
||||
$stmt = $pdo->query("
|
||||
SELECT p.*,
|
||||
(
|
||||
SELECT COUNT(*) FROM anggota_keluarga a WHERE a.penduduk_id = p.id
|
||||
) AS anggota_count
|
||||
FROM pemukiman_miskin_points p
|
||||
ORDER BY p.id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
} else {
|
||||
$stmt = $pdo->query("SELECT p.* FROM pemukiman_miskin_points p ORDER BY p.id ASC");
|
||||
$rows = $stmt->fetchAll();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$rows = [];
|
||||
}
|
||||
|
||||
// Best-effort: keep stored rumah_ibadah_id/jarak_meter in sync with latest radius rules.
|
||||
$upd = null;
|
||||
try {
|
||||
if ($hasRumahIbadahId && $hasJarak) {
|
||||
$upd = $pdo->prepare("UPDATE pemukiman_miskin_points SET rumah_ibadah_id = :rid, jarak_meter = :jarak WHERE id = :id");
|
||||
} elseif ($hasRumahIbadahId) {
|
||||
$upd = $pdo->prepare("UPDATE pemukiman_miskin_points SET rumah_ibadah_id = :rid WHERE id = :id");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$upd = null;
|
||||
}
|
||||
|
||||
foreach ($rows as &$p) {
|
||||
$lat = isset($p['latitude']) ? (float)$p['latitude'] : null;
|
||||
$lng = isset($p['longitude']) ? (float)$p['longitude'] : null;
|
||||
if ($lat === null || $lng === null) continue;
|
||||
|
||||
$best = compute_nearest_ibadah($ibadah, $lat, $lng);
|
||||
$rid = $best ? (int)$best['rumah_ibadah_id'] : null;
|
||||
$jarak = $best ? (float)$best['jarak_meter'] : null;
|
||||
$storedRid = isset($p['rumah_ibadah_id']) ? (int)$p['rumah_ibadah_id'] : null;
|
||||
|
||||
// overwrite output with authoritative computed coverage
|
||||
$p['rumah_ibadah_id'] = $rid;
|
||||
if ($hasJarak) $p['jarak_meter'] = $jarak;
|
||||
|
||||
if ($best && isset($best['row']) && is_array($best['row'])) {
|
||||
$p['rumah_ibadah_nama'] = $best['row']['nama'] ?? null;
|
||||
$p['rumah_ibadah_jenis'] = $best['row']['jenis'] ?? null;
|
||||
$p['rumah_ibadah_kontak'] = $best['row']['kontak'] ?? null;
|
||||
$p['rumah_ibadah_radius'] = $best['row']['radius_meter'] ?? null;
|
||||
} else {
|
||||
$p['rumah_ibadah_nama'] = null;
|
||||
$p['rumah_ibadah_jenis'] = null;
|
||||
$p['rumah_ibadah_kontak'] = null;
|
||||
$p['rumah_ibadah_radius'] = null;
|
||||
}
|
||||
|
||||
// best-effort DB sync
|
||||
try {
|
||||
if ($upd && ((string)$storedRid !== (string)$rid)) {
|
||||
if ($hasRumahIbadahId && $hasJarak) {
|
||||
$upd->execute([':rid' => $rid, ':jarak' => $jarak, ':id' => (int)$p['id']]);
|
||||
} elseif ($hasRumahIbadahId) {
|
||||
$upd->execute([':rid' => $rid, ':id' => (int)$p['id']]);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
unset($p);
|
||||
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
function normalizePointRows($rows) {
|
||||
foreach ($rows as &$row) {
|
||||
if ((!isset($row['latitude']) || $row['latitude'] === null) && isset($row['geom']) && is_string($row['geom'])) {
|
||||
$geom = json_decode($row['geom'], true);
|
||||
if (is_array($geom) && ($geom['type'] ?? '') === 'Point') {
|
||||
$coords = $geom['coordinates'] ?? null;
|
||||
if (is_array($coords) && count($coords) >= 2) {
|
||||
// GeoJSON: [lng, lat]
|
||||
$row['longitude'] = $coords[0];
|
||||
$row['latitude'] = $coords[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
try {
|
||||
// Preferred schema: has latitude/longitude columns.
|
||||
$stmt = $pdo->query("
|
||||
SELECT id, nama, no, deskripsi, status_24jam, latitude, longitude
|
||||
FROM spbu_points
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
try {
|
||||
// Fallback schema: latitude/longitude exists, geom stored as LONGTEXT GeoJSON.
|
||||
$stmt = $pdo->query("
|
||||
SELECT id, nama, no, deskripsi, status_24jam, latitude, longitude, geom
|
||||
FROM spbu_points
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
$rows = normalizePointRows($rows);
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
try {
|
||||
// Fallback schema: only geom LONGTEXT; derive latitude/longitude from GeoJSON.
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
id,
|
||||
nama,
|
||||
NULL AS no,
|
||||
NULL AS deskripsi,
|
||||
0 AS status_24jam,
|
||||
geom
|
||||
FROM spbu_points
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
$rows = normalizePointRows($rows);
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
try {
|
||||
// Fallback schema: spatial geom
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
id,
|
||||
nama,
|
||||
NULL AS no,
|
||||
NULL AS deskripsi,
|
||||
0 AS status_24jam,
|
||||
ST_Y(geom) AS latitude,
|
||||
ST_X(geom) AS longitude
|
||||
FROM spbu_points
|
||||
ORDER BY id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
// Last resort
|
||||
$stmt = $pdo->query("SELECT * FROM spbu_points ORDER BY id ASC");
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
// Include jumlah penduduk binaan per rumah ibadah
|
||||
$stmt = $pdo->query("
|
||||
SELECT r.*,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM pemukiman_miskin_points p
|
||||
WHERE p.rumah_ibadah_id = r.id
|
||||
) AS binaan_count
|
||||
FROM rumah_ibadah_points r
|
||||
ORDER BY r.id ASC
|
||||
");
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$lat = isset($_GET['lat']) ? (float)$_GET['lat'] : null;
|
||||
$lng = isset($_GET['lng']) ? (float)$_GET['lng'] : null;
|
||||
|
||||
if ($lat === null || $lng === null) {
|
||||
echo json_encode(["success" => false, "message" => "lat/lng wajib diisi"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Nominatim usage policy requires a valid User-Agent identifying the application.
|
||||
$url = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=" . urlencode($lat) . "&lon=" . urlencode($lng);
|
||||
|
||||
try {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 12);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"User-Agent: webgis-spbu/1.0 (XAMPP PHP reverse geocode)"
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($resp === false || $code < 200 || $code >= 300) {
|
||||
echo json_encode(["success" => false, "message" => $err ?: ("HTTP " . $code)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$json = json_decode($resp, true);
|
||||
$display = $json['display_name'] ?? null;
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"address" => $display,
|
||||
"raw" => $json
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Data kosong"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$nama_jalan = trim($data['nama_jalan'] ?? '');
|
||||
$status_jalan = trim($data['status_jalan'] ?? '');
|
||||
$panjang_meter = (float)($data['panjang_meter'] ?? 0);
|
||||
$geometry = $data['geometry'] ?? null;
|
||||
|
||||
if ($id <= 0 || $nama_jalan === '' || $status_jalan === '' || !$geometry) {
|
||||
echo json_encode(["success" => false, "message" => "Data update jalan belum lengkap"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE jalan_lines
|
||||
SET nama_jalan = :nama_jalan,
|
||||
status_jalan = :status_jalan,
|
||||
panjang_meter = :panjang_meter,
|
||||
geojson = :geojson
|
||||
WHERE id = :id";
|
||||
|
||||
$geojson = json_encode($geometry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':nama_jalan' => $nama_jalan,
|
||||
':status_jalan' => $status_jalan,
|
||||
':panjang_meter' => $panjang_meter,
|
||||
':geojson' => $geojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data jalan berhasil diupdate"]);
|
||||
exit;
|
||||
} catch (Exception $inner) {
|
||||
// fallback to spatial geom schema
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE jalan_lines
|
||||
SET nama_jalan = :nama_jalan,
|
||||
status_jalan = :status_jalan,
|
||||
panjang_meter = :panjang_meter,
|
||||
geom = ST_GeomFromGeoJSON(:geojson)
|
||||
WHERE id = :id");
|
||||
|
||||
$stmt->execute([
|
||||
':nama_jalan' => $nama_jalan,
|
||||
':status_jalan' => $status_jalan,
|
||||
':panjang_meter' => $panjang_meter,
|
||||
':geojson' => $geojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data jalan berhasil diupdate"]);
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Data kosong"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$status_kepemilikan = trim($data['status_kepemilikan'] ?? '');
|
||||
$luas_m2 = (float)($data['luas_m2'] ?? 0);
|
||||
$geometry = $data['geometry'] ?? null;
|
||||
|
||||
if ($id <= 0 || $status_kepemilikan === '' || !$geometry) {
|
||||
echo json_encode(["success" => false, "message" => "Data update parsil belum lengkap"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE parsil_tanah
|
||||
SET status_kepemilikan = :status_kepemilikan,
|
||||
luas_m2 = :luas_m2,
|
||||
geojson = :geojson
|
||||
WHERE id = :id";
|
||||
|
||||
$geojson = json_encode($geometry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':status_kepemilikan' => $status_kepemilikan,
|
||||
':luas_m2' => $luas_m2,
|
||||
':geojson' => $geojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data parsil berhasil diupdate"]);
|
||||
exit;
|
||||
} catch (Exception $inner) {
|
||||
// fallback to spatial geom schema
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE parsil_tanah
|
||||
SET status_kepemilikan = :status_kepemilikan,
|
||||
luas_m2 = :luas_m2,
|
||||
geom = ST_GeomFromGeoJSON(:geojson)
|
||||
WHERE id = :id");
|
||||
|
||||
$stmt->execute([
|
||||
':status_kepemilikan' => $status_kepemilikan,
|
||||
':luas_m2' => $luas_m2,
|
||||
':geojson' => $geojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data parsil berhasil diupdate"]);
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
function haversine_m($lat1, $lon1, $lat2, $lon2) {
|
||||
$R = 6371000.0;
|
||||
$phi1 = deg2rad((float)$lat1);
|
||||
$phi2 = deg2rad((float)$lat2);
|
||||
$dphi = deg2rad((float)$lat2 - (float)$lat1);
|
||||
$dlambda = deg2rad((float)$lon2 - (float)$lon1);
|
||||
$a = sin($dphi/2) * sin($dphi/2) + cos($phi1) * cos($phi2) * sin($dlambda/2) * sin($dlambda/2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
return $R * $c;
|
||||
}
|
||||
|
||||
function get_payload() {
|
||||
$ct = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
if (stripos($ct, 'multipart/form-data') !== false) {
|
||||
return [
|
||||
'id' => $_POST['id'] ?? 0,
|
||||
'kk_nama' => $_POST['kk_nama'] ?? '',
|
||||
'nik' => $_POST['nik'] ?? '',
|
||||
'jumlah_anggota' => $_POST['jumlah_anggota'] ?? '',
|
||||
'latitude' => $_POST['latitude'] ?? null,
|
||||
'longitude' => $_POST['longitude'] ?? null,
|
||||
'address' => $_POST['address'] ?? '',
|
||||
'kelurahan' => $_POST['kelurahan'] ?? '',
|
||||
'kecamatan' => $_POST['kecamatan'] ?? '',
|
||||
'status_bantuan' => $_POST['status_bantuan'] ?? 'Belum dibantu',
|
||||
'jenis_bantuan' => $_POST['jenis_bantuan'] ?? '',
|
||||
'tanggal_bantuan' => $_POST['tanggal_bantuan'] ?? '',
|
||||
'anggota' => $_POST['anggota'] ?? '[]'
|
||||
];
|
||||
}
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
return is_array($json) ? $json : null;
|
||||
}
|
||||
|
||||
function compute_nearest_ibadah(PDO $pdo, $lat, $lng) {
|
||||
$stmt = $pdo->query("SELECT id, latitude, longitude, radius_meter FROM rumah_ibadah_points");
|
||||
$rows = $stmt->fetchAll();
|
||||
$best = null;
|
||||
foreach ($rows as $r) {
|
||||
$rLat = (float)$r['latitude'];
|
||||
$rLng = (float)$r['longitude'];
|
||||
$radius = (float)$r['radius_meter'];
|
||||
if ($radius <= 0) continue;
|
||||
$d = haversine_m($lat, $lng, $rLat, $rLng);
|
||||
if ($d <= $radius) {
|
||||
if ($best === null || $d < $best['jarak_meter']) {
|
||||
$best = ['rumah_ibadah_id' => (int)$r['id'], 'jarak_meter' => $d];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best ?: ['rumah_ibadah_id' => null, 'jarak_meter' => null];
|
||||
}
|
||||
|
||||
function save_upload_if_any() {
|
||||
if (!isset($_FILES['bukti_file']) || !is_array($_FILES['bukti_file'])) return null;
|
||||
if (($_FILES['bukti_file']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return null;
|
||||
$dir = __DIR__ . '/../uploads/bukti';
|
||||
if (!is_dir($dir)) @mkdir($dir, 0775, true);
|
||||
if (!is_dir($dir)) return null;
|
||||
|
||||
$orig = (string)($_FILES['bukti_file']['name'] ?? 'file');
|
||||
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
|
||||
$allowed = ['jpg','jpeg','png','webp','pdf'];
|
||||
if ($ext && !in_array($ext, $allowed, true)) return null;
|
||||
|
||||
$safeBase = preg_replace('/[^a-zA-Z0-9._-]+/', '_', pathinfo($orig, PATHINFO_FILENAME));
|
||||
$safeBase = trim($safeBase, '_');
|
||||
if ($safeBase === '') $safeBase = 'bukti';
|
||||
$filename = $safeBase . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . ($ext ? ('.' . $ext) : '');
|
||||
$dest = $dir . '/' . $filename;
|
||||
if (!move_uploaded_file($_FILES['bukti_file']['tmp_name'], $dest)) return null;
|
||||
return 'uploads/bukti/' . $filename;
|
||||
}
|
||||
|
||||
$data = get_payload();
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Payload kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$kk_nama = trim($data['kk_nama'] ?? $data['nama'] ?? '');
|
||||
$nik = trim($data['nik'] ?? '');
|
||||
$jumlah_anggota = (int)($data['jumlah_anggota'] ?? 0);
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
$address = trim($data['address'] ?? '');
|
||||
$kelurahan = trim($data['kelurahan'] ?? '');
|
||||
$kecamatan = trim($data['kecamatan'] ?? '');
|
||||
$status_bantuan = trim($data['status_bantuan'] ?? 'Belum dibantu');
|
||||
$jenis_bantuan = trim($data['jenis_bantuan'] ?? '');
|
||||
$tanggal_bantuan = trim($data['tanggal_bantuan'] ?? '');
|
||||
$anggotaRaw = $data['anggota'] ?? [];
|
||||
|
||||
$validStatus = ['Belum dibantu', 'Sudah dibantu', 'Menunggu verifikasi'];
|
||||
if (!in_array($status_bantuan, $validStatus, true)) $status_bantuan = 'Belum dibantu';
|
||||
|
||||
if ($id <= 0 || $kk_nama === '' || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data update penduduk miskin belum lengkap."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$lat = (float)$latitude;
|
||||
$lng = (float)$longitude;
|
||||
$nearest = compute_nearest_ibadah($pdo, $lat, $lng);
|
||||
$uploadPath = save_upload_if_any();
|
||||
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($hasNew) {
|
||||
if ($uploadPath === null) {
|
||||
$stmt = $pdo->prepare("SELECT bukti_file FROM pemukiman_miskin_points WHERE id = :id");
|
||||
$stmt->execute([':id' => $id]);
|
||||
$uploadPath = $stmt->fetchColumn() ?: null;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE pemukiman_miskin_points
|
||||
SET kk_nama = :kk_nama,
|
||||
nik = :nik,
|
||||
jumlah_anggota = :jumlah_anggota,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude,
|
||||
address = :address,
|
||||
kelurahan = :kelurahan,
|
||||
kecamatan = :kecamatan,
|
||||
status_bantuan = :status_bantuan,
|
||||
jenis_bantuan = :jenis_bantuan,
|
||||
tanggal_bantuan = :tanggal_bantuan,
|
||||
bukti_file = :bukti_file,
|
||||
jarak_meter = :jarak_meter,
|
||||
rumah_ibadah_id = :rumah_ibadah_id
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':kk_nama' => $kk_nama,
|
||||
':nik' => ($nik === '' ? null : $nik),
|
||||
':jumlah_anggota' => $jumlah_anggota,
|
||||
':latitude' => $lat,
|
||||
':longitude' => $lng,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':kelurahan' => ($kelurahan === '' ? null : $kelurahan),
|
||||
':kecamatan' => ($kecamatan === '' ? null : $kecamatan),
|
||||
':status_bantuan' => $status_bantuan,
|
||||
':jenis_bantuan' => ($jenis_bantuan === '' ? null : $jenis_bantuan),
|
||||
':tanggal_bantuan' => ($tanggal_bantuan === '' ? null : $tanggal_bantuan),
|
||||
':bukti_file' => $uploadPath,
|
||||
':jarak_meter' => $nearest['jarak_meter'],
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id'],
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
// anggota keluarga (replace)
|
||||
$pdo->prepare("DELETE FROM anggota_keluarga WHERE penduduk_id = :id")->execute([':id' => $id]);
|
||||
$anggota = is_string($anggotaRaw) ? json_decode($anggotaRaw, true) : $anggotaRaw;
|
||||
if (is_array($anggota) && count($anggota) > 0) {
|
||||
$ins = $pdo->prepare("
|
||||
INSERT INTO anggota_keluarga (penduduk_id, nama, hubungan, umur, pekerjaan, keterangan)
|
||||
VALUES (:penduduk_id, :nama, :hubungan, :umur, :pekerjaan, :keterangan)
|
||||
");
|
||||
foreach ($anggota as $a) {
|
||||
$n = trim((string)($a['nama'] ?? ''));
|
||||
$h = trim((string)($a['hubungan'] ?? ''));
|
||||
if ($n === '' || $h === '') continue;
|
||||
$ins->execute([
|
||||
':penduduk_id' => $id,
|
||||
':nama' => $n,
|
||||
':hubungan' => $h,
|
||||
':umur' => isset($a['umur']) && $a['umur'] !== '' ? (int)$a['umur'] : null,
|
||||
':pekerjaan' => ($a['pekerjaan'] ?? '') === '' ? null : (string)$a['pekerjaan'],
|
||||
':keterangan' => ($a['keterangan'] ?? '') === '' ? null : (string)$a['keterangan']
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE pemukiman_miskin_points
|
||||
SET nama = :nama,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude,
|
||||
address = :address,
|
||||
rumah_ibadah_id = :rumah_ibadah_id
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $kk_nama,
|
||||
':latitude' => $lat,
|
||||
':longitude' => $lng,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id'],
|
||||
':id' => $id
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"rumah_ibadah_id" => $nearest['rumah_ibadah_id'],
|
||||
"jarak_meter" => $nearest['jarak_meter'],
|
||||
"message" => "Penduduk miskin berhasil diupdate"
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
function haversine_m($lat1, $lon1, $lat2, $lon2) {
|
||||
$R = 6371000.0;
|
||||
$phi1 = deg2rad((float)$lat1);
|
||||
$phi2 = deg2rad((float)$lat2);
|
||||
$dphi = deg2rad((float)$lat2 - (float)$lat1);
|
||||
$dlambda = deg2rad((float)$lon2 - (float)$lon1);
|
||||
$a = sin($dphi/2) * sin($dphi/2) + cos($phi1) * cos($phi2) * sin($dlambda/2) * sin($dlambda/2);
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
return $R * $c;
|
||||
}
|
||||
|
||||
function compute_nearest_ibadah(PDO $pdo, $lat, $lng) {
|
||||
$stmt = $pdo->query("SELECT id, latitude, longitude, radius_meter FROM rumah_ibadah_points");
|
||||
$rows = $stmt->fetchAll();
|
||||
$best = null;
|
||||
foreach ($rows as $r) {
|
||||
$rLat = (float)$r['latitude'];
|
||||
$rLng = (float)$r['longitude'];
|
||||
$radius = (float)$r['radius_meter'];
|
||||
if ($radius <= 0) continue;
|
||||
$d = haversine_m($lat, $lng, $rLat, $rLng);
|
||||
if ($d <= $radius) {
|
||||
if ($best === null || $d < $best['jarak_meter']) {
|
||||
$best = ['rumah_ibadah_id' => (int)$r['id'], 'jarak_meter' => $d];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best ?: ['rumah_ibadah_id' => null, 'jarak_meter' => null];
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!is_array($data)) {
|
||||
echo json_encode(["success" => false, "message" => "Payload JSON kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$lat = isset($data['latitude']) ? (float)$data['latitude'] : null;
|
||||
$lng = isset($data['longitude']) ? (float)$data['longitude'] : null;
|
||||
if ($id <= 0 || $lat === null || $lng === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data update lokasi belum lengkap (id/lat/lng)."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$nearest = compute_nearest_ibadah($pdo, $lat, $lng);
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM pemukiman_miskin_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasNew = in_array('kk_nama', $cols, true);
|
||||
|
||||
if ($hasNew) {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE pemukiman_miskin_points
|
||||
SET latitude = :lat,
|
||||
longitude = :lng,
|
||||
jarak_meter = :jarak_meter,
|
||||
rumah_ibadah_id = :rumah_ibadah_id
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':jarak_meter' => $nearest['jarak_meter'],
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id'],
|
||||
':id' => $id
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE pemukiman_miskin_points
|
||||
SET latitude = :lat,
|
||||
longitude = :lng,
|
||||
rumah_ibadah_id = :rumah_ibadah_id
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':lat' => $lat,
|
||||
':lng' => $lng,
|
||||
':rumah_ibadah_id' => $nearest['rumah_ibadah_id'],
|
||||
':id' => $id
|
||||
]);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"rumah_ibadah_id" => $nearest['rumah_ibadah_id'],
|
||||
"jarak_meter" => $nearest['jarak_meter'],
|
||||
"message" => "Lokasi penduduk miskin berhasil diupdate"
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Data kosong"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$nama = trim($data['nama'] ?? '');
|
||||
$no = trim($data['no'] ?? '');
|
||||
$deskripsi = trim($data['deskripsi'] ?? '');
|
||||
$status_24jam = isset($data['status_24jam']) ? (int)$data['status_24jam'] : 0;
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
|
||||
if ($id <= 0 || $nama === '' || $no === '' || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data update point belum lengkap"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE spbu_points
|
||||
SET nama = :nama,
|
||||
no = :no,
|
||||
deskripsi = :deskripsi,
|
||||
status_24jam = :status_24jam,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude
|
||||
WHERE id = :id";
|
||||
|
||||
$pointGeojson = json_encode([
|
||||
"type" => "Point",
|
||||
"coordinates" => [(float)$longitude, (float)$latitude]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':no' => $no,
|
||||
':deskripsi' => $deskripsi,
|
||||
':status_24jam' => $status_24jam,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data point berhasil diupdate"]);
|
||||
exit;
|
||||
} catch (Exception $inner) {
|
||||
// continue fallback
|
||||
}
|
||||
|
||||
// Fallback schema: geom LONGTEXT
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE spbu_points
|
||||
SET nama = :nama,
|
||||
no = :no,
|
||||
deskripsi = :deskripsi,
|
||||
status_24jam = :status_24jam,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude,
|
||||
geom = :geom
|
||||
WHERE id = :id");
|
||||
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':no' => $no,
|
||||
':deskripsi' => $deskripsi,
|
||||
':status_24jam' => $status_24jam,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':geom' => $pointGeojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data point berhasil diupdate"]);
|
||||
exit;
|
||||
} catch (Exception $inner2) {
|
||||
// continue fallback
|
||||
}
|
||||
|
||||
// Fallback schema: only (nama, geom) where geom is LONGTEXT GeoJSON
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE spbu_points
|
||||
SET nama = :nama,
|
||||
geom = :geom
|
||||
WHERE id = :id");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':geom' => $pointGeojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data point berhasil diupdate"]);
|
||||
exit;
|
||||
} catch (Exception $inner3) {
|
||||
// continue fallback
|
||||
}
|
||||
|
||||
// Fallback schema: spatial geom
|
||||
$stmt = $pdo->prepare("UPDATE spbu_points
|
||||
SET nama = :nama,
|
||||
geom = ST_GeomFromGeoJSON(:geom)
|
||||
WHERE id = :id");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':geom' => $pointGeojson,
|
||||
':id' => $id
|
||||
]);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Data point berhasil diupdate"]);
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once 'koneksi.php';
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$data) {
|
||||
echo json_encode(["success" => false, "message" => "Payload JSON kosong atau tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$nama = trim($data['nama'] ?? '');
|
||||
$jenis = trim($data['jenis'] ?? 'Lainnya');
|
||||
$kontak = trim($data['kontak'] ?? '');
|
||||
$radius_meter = (float)($data['radius_meter'] ?? 0);
|
||||
$latitude = $data['latitude'] ?? null;
|
||||
$longitude = $data['longitude'] ?? null;
|
||||
$address = trim($data['address'] ?? '');
|
||||
|
||||
if ($id <= 0 || $nama === '' || $radius_meter <= 0 || $latitude === null || $longitude === null) {
|
||||
echo json_encode(["success" => false, "message" => "Data update rumah ibadah belum lengkap."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM rumah_ibadah_points")->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
$hasJenis = in_array('jenis', $cols, true);
|
||||
$hasKontak = in_array('kontak', $cols, true);
|
||||
|
||||
if ($hasJenis && $hasKontak) {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE rumah_ibadah_points
|
||||
SET nama = :nama,
|
||||
jenis = :jenis,
|
||||
kontak = :kontak,
|
||||
radius_meter = :radius_meter,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude,
|
||||
address = :address
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':jenis' => ($jenis === '' ? 'Lainnya' : $jenis),
|
||||
':kontak' => ($kontak === '' ? null : $kontak),
|
||||
':radius_meter' => $radius_meter,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':id' => $id
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE rumah_ibadah_points
|
||||
SET nama = :nama,
|
||||
radius_meter = :radius_meter,
|
||||
latitude = :latitude,
|
||||
longitude = :longitude,
|
||||
address = :address
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->execute([
|
||||
':nama' => $nama,
|
||||
':radius_meter' => $radius_meter,
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':address' => ($address === '' ? null : $address),
|
||||
':id' => $id
|
||||
]);
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Rumah ibadah berhasil diupdate"]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
-- Schema WebGIS SPBU + Jalan + Parsil (MySQL / MariaDB)
|
||||
-- Catatan:
|
||||
-- - Data LINE/POLYGON disimpan sebagai GeoJSON geometry (tipe: geojson) di kolom LONGTEXT `geojson`.
|
||||
-- - Panjang (meter) dan luas (m2) dihitung otomatis di LeafletJS, bukan input manual.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS spbu_points (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(150) NOT NULL,
|
||||
no VARCHAR(60) NOT NULL,
|
||||
deskripsi TEXT NULL,
|
||||
status_24jam TINYINT(1) NOT NULL DEFAULT 0,
|
||||
latitude DECIMAL(10,7) NOT NULL,
|
||||
longitude DECIMAL(10,7) NOT NULL,
|
||||
-- opsional (kompatibilitas): simpan GeoJSON Point sebagai string
|
||||
geom LONGTEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_spbu_status24 (status_24jam),
|
||||
INDEX idx_spbu_latlng (latitude, longitude)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS jalan_lines (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(200) NOT NULL,
|
||||
status_jalan VARCHAR(20) NOT NULL, -- Nasional | Provinsi | Kabupaten
|
||||
panjang_meter DOUBLE NOT NULL DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL, -- GeoJSON geometry: { "type":"LineString", "coordinates":[...] }
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_jalan_status (status_jalan)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS parsil_tanah (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
status_kepemilikan VARCHAR(10) NOT NULL, -- SHM | HGB | HGU | HP
|
||||
luas_m2 DOUBLE NOT NULL DEFAULT 0,
|
||||
geojson LONGTEXT NOT NULL, -- GeoJSON geometry: { "type":"Polygon", "coordinates":[...] }
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_parsil_status (status_kepemilikan)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rumah_ibadah_points (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(200) NOT NULL,
|
||||
jenis VARCHAR(30) NOT NULL DEFAULT 'Lainnya', -- Masjid|Musholla|Gereja|Katolik|Pura|Vihara|Klenteng|Lainnya
|
||||
kontak VARCHAR(120) NULL,
|
||||
radius_meter DOUBLE NOT NULL DEFAULT 300,
|
||||
latitude DECIMAL(10,7) NOT NULL,
|
||||
longitude DECIMAL(10,7) NOT NULL,
|
||||
address TEXT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_ibadah_jenis (jenis),
|
||||
INDEX idx_ibadah_latlng (latitude, longitude)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pemukiman_miskin_points (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
kk_nama VARCHAR(200) NOT NULL,
|
||||
nik VARCHAR(32) NULL,
|
||||
jumlah_anggota INT NOT NULL DEFAULT 0,
|
||||
latitude DECIMAL(10,7) NOT NULL,
|
||||
longitude DECIMAL(10,7) NOT NULL,
|
||||
address TEXT NULL,
|
||||
kelurahan VARCHAR(120) NULL,
|
||||
kecamatan VARCHAR(120) NULL,
|
||||
status_bantuan VARCHAR(24) NOT NULL DEFAULT 'Belum dibantu', -- Belum dibantu|Sudah dibantu|Menunggu verifikasi
|
||||
jenis_bantuan VARCHAR(120) NULL,
|
||||
tanggal_bantuan DATE NULL,
|
||||
bukti_file VARCHAR(255) NULL,
|
||||
jarak_meter DOUBLE NULL,
|
||||
-- id rumah ibadah yang menaungi (hasil pencarian radius terdekat)
|
||||
rumah_ibadah_id INT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_miskin_latlng (latitude, longitude),
|
||||
INDEX idx_miskin_ibadah (rumah_ibadah_id),
|
||||
INDEX idx_miskin_status (status_bantuan),
|
||||
CONSTRAINT fk_miskin_ibadah FOREIGN KEY (rumah_ibadah_id) REFERENCES rumah_ibadah_points(id)
|
||||
ON DELETE SET NULL ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS anggota_keluarga (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
penduduk_id INT NOT NULL,
|
||||
nama VARCHAR(200) NOT NULL,
|
||||
hubungan VARCHAR(60) NOT NULL,
|
||||
umur INT NULL,
|
||||
pekerjaan VARCHAR(120) NULL,
|
||||
keterangan VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_anggota_penduduk (penduduk_id),
|
||||
CONSTRAINT fk_anggota_penduduk FOREIGN KEY (penduduk_id) REFERENCES pemukiman_miskin_points(id)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- (Opsional) User login untuk admin/operator.
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
username VARCHAR(60) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL DEFAULT 'operator', -- admin|operator
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Jika ingin strict value, bisa pakai ENUM, misal:
|
||||
-- status_jalan ENUM('Nasional','Provinsi','Kabupaten')
|
||||
-- status_kepemilikan ENUM('SHM','HGB','HGU','HP')
|
||||
|
||||
|
||||
-- ==========================================================
|
||||
-- MIGRATION (ALTER) UNTUK DATABASE LAMA (TANPA HILANGKAN DATA)
|
||||
-- ==========================================================
|
||||
-- Kapan perlu menjalankan bagian ini?
|
||||
-- - Jika database Anda dibuat dari project lama (kolom/tipe berbeda),
|
||||
-- misalnya masih pakai kolom spatial `geom` atau belum punya kolom
|
||||
-- `latitude/longitude/status_24jam/geojson`.
|
||||
-- - Jika database Anda sudah dibuat pakai schema di atas (CREATE TABLE),
|
||||
-- Anda biasanya TIDAK perlu ALTER apa-apa.
|
||||
--
|
||||
-- Cara pakai:
|
||||
-- - Jalankan file ini di phpMyAdmin / MySQL client.
|
||||
-- - Bagian CREATE TABLE aman (IF NOT EXISTS).
|
||||
-- - Bagian MIGRATION ini aman (cek INFORMATION_SCHEMA sebelum ALTER).
|
||||
|
||||
DELIMITER $$
|
||||
|
||||
DROP PROCEDURE IF EXISTS migrate_webgis_spbu $$
|
||||
CREATE PROCEDURE migrate_webgis_spbu()
|
||||
BEGIN
|
||||
-- ----------------------
|
||||
-- spbu_points
|
||||
-- ----------------------
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points'
|
||||
) THEN
|
||||
|
||||
-- Kolom atribut inti
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'no'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN no VARCHAR(60) NULL AFTER nama;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'deskripsi'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN deskripsi TEXT NULL AFTER no;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'status_24jam'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN status_24jam TINYINT(1) NOT NULL DEFAULT 0 AFTER deskripsi;
|
||||
END IF;
|
||||
|
||||
-- Lat/Lng: untuk menjaga data lama, dibuat NULL-able dulu.
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'latitude'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN latitude DECIMAL(10,7) NULL AFTER status_24jam;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'longitude'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN longitude DECIMAL(10,7) NULL AFTER latitude;
|
||||
END IF;
|
||||
|
||||
-- Kolom geom (opsional) untuk kompatibilitas legacy
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'geom'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD COLUMN geom LONGTEXT NULL AFTER longitude;
|
||||
END IF;
|
||||
|
||||
-- Index (jika belum ada)
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND index_name = 'idx_spbu_status24'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD INDEX idx_spbu_status24 (status_24jam);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND index_name = 'idx_spbu_latlng'
|
||||
) THEN
|
||||
ALTER TABLE spbu_points ADD INDEX idx_spbu_latlng (latitude, longitude);
|
||||
END IF;
|
||||
|
||||
-- Backfill latitude/longitude dari geom GeoJSON (jika geom berisi JSON valid)
|
||||
-- Catatan: ini hanya mengisi yang masih NULL supaya tidak menimpa data baru.
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'spbu_points' AND column_name = 'geom'
|
||||
) THEN
|
||||
-- Optional: JSON_VALID/JSON_EXTRACT bisa tidak tersedia pada versi tertentu.
|
||||
-- Best-effort: jika fungsi tidak didukung, blok ini di-skip tanpa menghentikan migrasi.
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
|
||||
UPDATE spbu_points
|
||||
SET
|
||||
longitude = CAST(JSON_UNQUOTE(JSON_EXTRACT(geom, '$.coordinates[0]')) AS DECIMAL(10,7)),
|
||||
latitude = CAST(JSON_UNQUOTE(JSON_EXTRACT(geom, '$.coordinates[1]')) AS DECIMAL(10,7))
|
||||
WHERE (latitude IS NULL OR longitude IS NULL)
|
||||
AND geom IS NOT NULL
|
||||
AND JSON_VALID(geom) = 1
|
||||
AND JSON_UNQUOTE(JSON_EXTRACT(geom, '$.type')) = 'Point';
|
||||
END;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- ----------------------
|
||||
-- jalan_lines
|
||||
-- ----------------------
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = 'jalan_lines'
|
||||
) THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'jalan_lines' AND column_name = 'panjang_meter'
|
||||
) THEN
|
||||
ALTER TABLE jalan_lines ADD COLUMN panjang_meter DOUBLE NOT NULL DEFAULT 0 AFTER status_jalan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'jalan_lines' AND column_name = 'geojson'
|
||||
) THEN
|
||||
ALTER TABLE jalan_lines ADD COLUMN geojson LONGTEXT NULL AFTER panjang_meter;
|
||||
END IF;
|
||||
|
||||
-- Backfill geojson dari kolom spatial geom (jika ada)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'jalan_lines' AND column_name = 'geom'
|
||||
) THEN
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
|
||||
UPDATE jalan_lines
|
||||
SET geojson = ST_AsGeoJSON(geom)
|
||||
WHERE (geojson IS NULL OR geojson = '') AND geom IS NOT NULL;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'jalan_lines' AND index_name = 'idx_jalan_status'
|
||||
) THEN
|
||||
ALTER TABLE jalan_lines ADD INDEX idx_jalan_status (status_jalan);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- ----------------------
|
||||
-- parsil_tanah
|
||||
-- ----------------------
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = 'parsil_tanah'
|
||||
) THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'parsil_tanah' AND column_name = 'luas_m2'
|
||||
) THEN
|
||||
ALTER TABLE parsil_tanah ADD COLUMN luas_m2 DOUBLE NOT NULL DEFAULT 0 AFTER status_kepemilikan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'parsil_tanah' AND column_name = 'geojson'
|
||||
) THEN
|
||||
ALTER TABLE parsil_tanah ADD COLUMN geojson LONGTEXT NULL AFTER luas_m2;
|
||||
END IF;
|
||||
|
||||
-- Backfill geojson dari kolom spatial geom (jika ada)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'parsil_tanah' AND column_name = 'geom'
|
||||
) THEN
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
|
||||
UPDATE parsil_tanah
|
||||
SET geojson = ST_AsGeoJSON(geom)
|
||||
WHERE (geojson IS NULL OR geojson = '') AND geom IS NOT NULL;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'parsil_tanah' AND index_name = 'idx_parsil_status'
|
||||
) THEN
|
||||
ALTER TABLE parsil_tanah ADD INDEX idx_parsil_status (status_kepemilikan);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- ----------------------
|
||||
-- rumah_ibadah_points
|
||||
-- ----------------------
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points'
|
||||
) THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points' AND column_name = 'jenis'
|
||||
) THEN
|
||||
ALTER TABLE rumah_ibadah_points ADD COLUMN jenis VARCHAR(30) NOT NULL DEFAULT 'Lainnya' AFTER nama;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points' AND column_name = 'kontak'
|
||||
) THEN
|
||||
ALTER TABLE rumah_ibadah_points ADD COLUMN kontak VARCHAR(120) NULL AFTER jenis;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points' AND column_name = 'radius_meter'
|
||||
) THEN
|
||||
ALTER TABLE rumah_ibadah_points ADD COLUMN radius_meter DOUBLE NOT NULL DEFAULT 300 AFTER kontak;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points' AND column_name = 'address'
|
||||
) THEN
|
||||
ALTER TABLE rumah_ibadah_points ADD COLUMN address TEXT NULL AFTER longitude;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'rumah_ibadah_points' AND index_name = 'idx_ibadah_latlng'
|
||||
) THEN
|
||||
ALTER TABLE rumah_ibadah_points ADD INDEX idx_ibadah_latlng (latitude, longitude);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- ----------------------
|
||||
-- pemukiman_miskin_points
|
||||
-- ----------------------
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points'
|
||||
) THEN
|
||||
-- Backward compat: kalau masih pakai kolom `nama`, migrasikan ke `kk_nama`
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'kk_nama'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN kk_nama VARCHAR(200) NULL AFTER id;
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
|
||||
UPDATE pemukiman_miskin_points
|
||||
SET kk_nama = COALESCE(NULLIF(kk_nama, ''), NULLIF(nama, ''))
|
||||
WHERE (kk_nama IS NULL OR kk_nama = '');
|
||||
END;
|
||||
-- jadikan NOT NULL kalau sudah terisi
|
||||
BEGIN
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
|
||||
ALTER TABLE pemukiman_miskin_points MODIFY kk_nama VARCHAR(200) NOT NULL;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'nik'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN nik VARCHAR(32) NULL AFTER kk_nama;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'jumlah_anggota'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN jumlah_anggota INT NOT NULL DEFAULT 0 AFTER nik;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'kelurahan'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN kelurahan VARCHAR(120) NULL AFTER address;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'kecamatan'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN kecamatan VARCHAR(120) NULL AFTER kelurahan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'status_bantuan'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN status_bantuan VARCHAR(24) NOT NULL DEFAULT 'Belum dibantu' AFTER kecamatan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'jenis_bantuan'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN jenis_bantuan VARCHAR(120) NULL AFTER status_bantuan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'tanggal_bantuan'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN tanggal_bantuan DATE NULL AFTER jenis_bantuan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'bukti_file'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN bukti_file VARCHAR(255) NULL AFTER tanggal_bantuan;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'jarak_meter'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN jarak_meter DOUBLE NULL AFTER bukti_file;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND column_name = 'rumah_ibadah_id'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD COLUMN rumah_ibadah_id INT NULL AFTER jarak_meter;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND index_name = 'idx_miskin_ibadah'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD INDEX idx_miskin_ibadah (rumah_ibadah_id);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'pemukiman_miskin_points' AND index_name = 'idx_miskin_latlng'
|
||||
) THEN
|
||||
ALTER TABLE pemukiman_miskin_points ADD INDEX idx_miskin_latlng (latitude, longitude);
|
||||
END IF;
|
||||
END IF;
|
||||
END $$
|
||||
|
||||
-- Jalankan migrasi (aman dijalankan berulang)
|
||||
CALL migrate_webgis_spbu() $$
|
||||
DROP PROCEDURE IF EXISTS migrate_webgis_spbu $$
|
||||
|
||||
DELIMITER ;
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user