feat: implement full authentication system and CRUD APIs for users, rumah ibadah, and penduduk miskin with database updates.
@@ -1,17 +1,26 @@
|
|||||||
# WebGIS Pemetaan Kemiskinan Kota Pontianak
|
# WebGIS Pemetaan Kemiskinan Kota Pontianak
|
||||||
|
|
||||||
Aplikasi WebGIS sederhana berbasis Leaflet.js untuk memetakan data kemiskinan, sebaran penduduk miskin, log bantuan, rumah ibadah, SPBU, serta data jalan dan parsil tanah di Kota Pontianak.
|
Aplikasi WebGIS sederhana berbasis Leaflet.js untuk memetakan data kemiskinan, sebaran penduduk miskin, log bantuan, dan rumah ibadah di Kota Pontianak.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> **DISCLAIMER — DATA DUMMY / BUKAN DATA NYATA**
|
||||||
|
>
|
||||||
|
> Seluruh data yang ditampilkan dalam aplikasi ini, termasuk namun tidak terbatas pada:
|
||||||
|
> - Data nama, lokasi, dan kategori **penduduk miskin**
|
||||||
|
> - **Foto rumah** dan **Foto Kartu Keluarga (KK)** yang diunggah
|
||||||
|
> - Data **rumah ibadah** beserta radius jangkauannya
|
||||||
|
> - **Log bantuan** yang tercatat di sistem
|
||||||
|
>
|
||||||
|
> adalah **data fiktif / dummy** yang dibuat semata-mata untuk keperluan **demonstrasi, pengembangan, dan pengujian sistem** ini.
|
||||||
|
> Data tersebut **tidak mewakili kondisi, identitas, atau situasi nyata** dari individu maupun lokasi mana pun di Kota Pontianak.
|
||||||
|
|
||||||
## Fitur Utama
|
## Fitur Utama
|
||||||
|
|
||||||
- **Pemetaan Interaktif**: Visualisasi peta dengan Leaflet.js yang berpusat di Kota Pontianak.
|
- **Pemetaan Interaktif**: Visualisasi peta dengan Leaflet.js yang berpusat di Kota Pontianak.
|
||||||
- **Manajemen Data Spasial**:
|
- **Manajemen Data Spasial**:
|
||||||
- **SPBU**: Lokasi titik SPBU beserta informasi layanan 24 jam.
|
|
||||||
- **Jalan**: Fitur line/polyline dengan kategori status jalan (Nasional, Provinsi, Kabupaten).
|
|
||||||
- **Parsil**: Fitur area/polygon tanah dengan kategori sertifikasi (SHM, HGB, HGU, HP).
|
|
||||||
- **Rumah Ibadah**: Lokasi titik rumah ibadah (Masjid, Gereja, Vihara, dll) beserta jangkauan radius jemaah/bantuan.
|
- **Rumah Ibadah**: Lokasi titik rumah ibadah (Masjid, Gereja, Vihara, dll) beserta jangkauan radius jemaah/bantuan.
|
||||||
- **Penduduk Miskin**: Pemetaan warga kurang mampu dan integrasi log/riwayat bantuan sosial yang disalurkan melalui rumah ibadah terdekat.
|
- **Penduduk Miskin**: Pemetaan warga kurang mampu dan integrasi log/riwayat bantuan sosial yang disalurkan melalui rumah ibadah terdekat.
|
||||||
- **Pencarian Cepat**: Cari data jalan, parsil, SPBU, atau rumah ibadah langsung dari bilah pencarian.
|
- **Pencarian Cepat**: Cari data rumah ibadah langsung dari bilah pencarian.
|
||||||
- **Impor GeoJSON Eksternal**: Pengguna dapat menambahkan file GeoJSON dari perangkat lokal ke dalam peta.
|
- **Impor GeoJSON Eksternal**: Pengguna dapat menambahkan file GeoJSON dari perangkat lokal ke dalam peta.
|
||||||
|
|
||||||
## Persyaratan Sistem
|
## Persyaratan Sistem
|
||||||
@@ -48,13 +57,28 @@ Aplikasi WebGIS sederhana berbasis Leaflet.js untuk memetakan data kemiskinan, s
|
|||||||
- Buka browser dan navigasikan ke alamat:
|
- Buka browser dan navigasikan ke alamat:
|
||||||
[http://localhost/webgis/webgis-poverty-pontianak/index.php](http://localhost/webgis/webgis-poverty-pontianak/index.php)
|
[http://localhost/webgis/webgis-poverty-pontianak/index.php](http://localhost/webgis/webgis-poverty-pontianak/index.php)
|
||||||
|
|
||||||
|
## Akun Akses Default (Login)
|
||||||
|
|
||||||
|
Anda dapat masuk ke dalam sistem menggunakan akun simulasi berikut:
|
||||||
|
|
||||||
|
- **Akun Administrator (Admin)**:
|
||||||
|
- **Username**: `admin`
|
||||||
|
- **Password**: `password`
|
||||||
|
- **Hak Akses**: Mengelola data spasial rumah ibadah, penduduk miskin, mengelola akun pengelola rumah ibadah, dan manajemen pengguna secara penuh.
|
||||||
|
|
||||||
|
- **Akun Pengelola Rumah Ibadah**:
|
||||||
|
- **Username**: `pengelola_mujahidin`
|
||||||
|
- **Password**: `password`
|
||||||
|
- **Hak Akses**: Mengelola (tambah, edit, hapus, impor, geser lokasi) data penduduk miskin, serta menginput log bantuan sosial yang disalurkan melalui masjid kelolaannya (Masjid Raya Mujahidin).
|
||||||
|
|
||||||
## Struktur Folder
|
## Struktur Folder
|
||||||
|
|
||||||
- `api/` — Kumpulan REST API endpoint untuk operasi CRUD masing-masing fitur.
|
- `api/` — Kumpulan REST API endpoint untuk operasi CRUD masing-masing fitur.
|
||||||
- `jalan/`, `parsil/`, `spbu/`, `rumah_ibadah/`, `penduduk_miskin/`, `log_bantuan/`
|
- `rumah_ibadah/`, `penduduk_miskin/`, `log_bantuan/`
|
||||||
- `assets/` — File aset frontend.
|
- `assets/` — File aset frontend.
|
||||||
- `css/` — Gaya UI (custom style.css).
|
- `css/` — Gaya UI (custom style.css).
|
||||||
- `js/` — Logika frontend peta dan pengelolaan interaksi panel data.
|
- `js/` — Logika frontend peta dan pengelolaan interaksi panel data.
|
||||||
- `features/` — Modul fitur Leaflet (menggambar peta, manajemen layer, geolokasi).
|
- `features/` — Modul fitur Leaflet (manajemen layer, geolokasi, dll).
|
||||||
- `index.php` — Halaman utama interface WebGIS.
|
- `index.php` — Halaman utama interface WebGIS.
|
||||||
- `database.sql` — Skema database beserta data dummy Pontianak untuk pengetesan awal.
|
- `database.sql` — Skema database beserta **data dummy** Pontianak untuk pengetesan awal. Seluruh data di dalamnya bersifat fiktif dan tidak merepresentasikan data nyata.
|
||||||
|
- `uploads/` — Folder penyimpanan foto yang diunggah. File yang sudah ada merupakan contoh dummy semata.
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLoggedIn() {
|
||||||
|
return isset($_SESSION['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentUser() {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'id' => $_SESSION['user_id'],
|
||||||
|
'username' => $_SESSION['username'],
|
||||||
|
'role' => $_SESSION['role'],
|
||||||
|
'ibadah_id' => $_SESSION['ibadah_id'] ? intval($_SESSION['ibadah_id']) : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireLogin() {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
header('HTTP/1.1 401 Unauthorized');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Unauthorized: Silakan login terlebih dahulu.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireAdmin() {
|
||||||
|
requireLogin();
|
||||||
|
if ($_SESSION['role'] !== 'admin') {
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Forbidden: Akses ditolak. Hanya Admin yang diizinkan.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireAdminOrPengelola() {
|
||||||
|
requireLogin();
|
||||||
|
if ($_SESSION['role'] !== 'admin' && $_SESSION['role'] !== 'pengelola') {
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Forbidden: Akses ditolak.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canManageIbadah($ibadah_id) {
|
||||||
|
if (!isLoggedIn()) return false;
|
||||||
|
if ($_SESSION['role'] === 'admin') return true;
|
||||||
|
if ($_SESSION['role'] === 'pengelola') {
|
||||||
|
return intval($_SESSION['ibadah_id']) === intval($ibadah_id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'isLoggedIn' => true,
|
||||||
|
'data' => [
|
||||||
|
'id' => intval($_SESSION['user_id']),
|
||||||
|
'username' => $_SESSION['username'],
|
||||||
|
'role' => $_SESSION['role'],
|
||||||
|
'ibadah_id' => $_SESSION['ibadah_id'] ? intval($_SESSION['ibadah_id']) : null,
|
||||||
|
'nama_ibadah' => $_SESSION['nama_ibadah'] ?? null
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'isLoggedIn' => false,
|
||||||
|
'data' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->geom)) {
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? 'Jalan Baru');
|
|
||||||
$status = $conn->real_escape_string($data->status ?? 'Kabupaten');
|
|
||||||
$panjang = (float)($data->panjang ?? 0);
|
|
||||||
$geom = $conn->real_escape_string(json_encode($data->geom)); // GeoJSON string
|
|
||||||
|
|
||||||
$query = "INSERT INTO jalan (nama, status, panjang, geom) VALUES ('$nama', '$status', $panjang, ST_GeomFromGeoJSON('$geom'))";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Jalan berhasil ditambahkan."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal menambahkan jalan: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Data geometri tidak lengkap."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
|
|
||||||
$query = "DELETE FROM jalan WHERE id=$id";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "Jalan berhasil dihapus."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal hapus jalan: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "ID tidak diberikan."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$query = "SELECT id, nama, status, panjang, ST_AsGeoJSON(geom) as geom FROM jalan";
|
|
||||||
$result = $conn->query($query);
|
|
||||||
|
|
||||||
$jalan_arr = array();
|
|
||||||
|
|
||||||
if($result->num_rows > 0) {
|
|
||||||
while($row = $result->fetch_assoc()) {
|
|
||||||
$jalan_item = array(
|
|
||||||
"id" => $row['id'],
|
|
||||||
"nama" => $row['nama'],
|
|
||||||
"status" => $row['status'],
|
|
||||||
"panjang" => (float)$row['panjang'],
|
|
||||||
"geom" => json_decode($row['geom'])
|
|
||||||
);
|
|
||||||
array_push($jalan_arr, $jalan_item);
|
|
||||||
}
|
|
||||||
echo json_encode(["status" => "success", "data" => $jalan_arr]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "success", "data" => []]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
|
||||||
$status = $conn->real_escape_string($data->status ?? 'Kabupaten');
|
|
||||||
|
|
||||||
// Jika ada update geometri
|
|
||||||
if(!empty($data->geom)) {
|
|
||||||
$panjang = (float)($data->panjang ?? 0);
|
|
||||||
$geom = $conn->real_escape_string(json_encode($data->geom));
|
|
||||||
$query = "UPDATE jalan SET nama='$nama', status='$status', panjang=$panjang, geom=ST_GeomFromGeoJSON('$geom') WHERE id=$id";
|
|
||||||
} else {
|
|
||||||
$query = "UPDATE jalan SET nama='$nama', status='$status' WHERE id=$id";
|
|
||||||
}
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "Jalan berhasil diupdate."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal update jalan: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "ID tidak diberikan."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdminOrPengelola();
|
||||||
require_once '../db_connect.php';
|
require_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
@@ -10,6 +12,14 @@ $tipe_bantuan = trim($data['tipe_bantuan'] ?? '');
|
|||||||
$tanggal = trim($data['tanggal'] ?? '');
|
$tanggal = trim($data['tanggal'] ?? '');
|
||||||
$keterangan = trim($data['keterangan'] ?? '');
|
$keterangan = trim($data['keterangan'] ?? '');
|
||||||
|
|
||||||
|
if ($_SESSION['role'] === 'pengelola') {
|
||||||
|
if ($ibadah_id !== intval($_SESSION['ibadah_id'])) {
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Forbidden: Anda tidak diizinkan membuat log untuk rumah ibadah lain.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$miskin_id || !$ibadah_id || !$tipe_bantuan || !$tanggal) {
|
if (!$miskin_id || !$ibadah_id || !$tipe_bantuan || !$tanggal) {
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
require_once 'db_connect.php';
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$username = trim($data['username'] ?? '');
|
||||||
|
$password = trim($data['password'] ?? '');
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username dan password wajib diisi']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cari user di database
|
||||||
|
$sql = "SELECT u.*, ri.nama as nama_ibadah
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN rumah_ibadah ri ON u.ibadah_id = ri.id
|
||||||
|
WHERE u.username = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param('s', $username);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows === 1) {
|
||||||
|
$user = $result->fetch_assoc();
|
||||||
|
// Verifikasi password
|
||||||
|
if (password_verify($password, $user['password'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['role'] = $user['role'];
|
||||||
|
$_SESSION['ibadah_id'] = $user['ibadah_id'];
|
||||||
|
$_SESSION['nama_ibadah'] = $user['nama_ibadah'];
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Login berhasil',
|
||||||
|
'data' => [
|
||||||
|
'id' => intval($user['id']),
|
||||||
|
'username' => $user['username'],
|
||||||
|
'role' => $user['role'],
|
||||||
|
'ibadah_id' => $user['ibadah_id'] ? intval($user['ibadah_id']) : null,
|
||||||
|
'nama_ibadah' => $user['nama_ibadah']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username atau password salah']);
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION = array();
|
||||||
|
|
||||||
|
if (ini_get("session.use_cookies")) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
setcookie(session_name(), '', time() - 42000,
|
||||||
|
$params["path"], $params["domain"],
|
||||||
|
$params["secure"], $params["httponly"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Logout berhasil']);
|
||||||
|
?>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->geom)) {
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
|
||||||
$status = $conn->real_escape_string($data->status ?? 'SHM');
|
|
||||||
$luas = (float)($data->luas ?? 0);
|
|
||||||
$geom = $conn->real_escape_string(json_encode($data->geom)); // GeoJSON string
|
|
||||||
|
|
||||||
$query = "INSERT INTO parsil (nama, status, luas, geom) VALUES ('$nama', '$status', $luas, ST_GeomFromGeoJSON('$geom'))";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Parsil berhasil ditambahkan."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal menambahkan parsil: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Data geometri tidak lengkap."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
|
|
||||||
$query = "DELETE FROM parsil WHERE id=$id";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "Parsil berhasil dihapus."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal hapus parsil: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "ID tidak diberikan."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$query = "SELECT id, nama, status, luas, ST_AsGeoJSON(geom) as geom FROM parsil";
|
|
||||||
$result = $conn->query($query);
|
|
||||||
|
|
||||||
$parsil_arr = array();
|
|
||||||
|
|
||||||
if($result->num_rows > 0) {
|
|
||||||
while($row = $result->fetch_assoc()) {
|
|
||||||
$parsil_item = array(
|
|
||||||
"id" => $row['id'],
|
|
||||||
"nama" => $row['nama'],
|
|
||||||
"status" => $row['status'],
|
|
||||||
"luas" => (float)$row['luas'],
|
|
||||||
"geom" => json_decode($row['geom'])
|
|
||||||
);
|
|
||||||
array_push($parsil_arr, $parsil_item);
|
|
||||||
}
|
|
||||||
echo json_encode(["status" => "success", "data" => $parsil_arr]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "success", "data" => []]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
|
||||||
$status = $conn->real_escape_string($data->status ?? 'SHM');
|
|
||||||
|
|
||||||
// Jika ada update geometri
|
|
||||||
if(!empty($data->geom)) {
|
|
||||||
$luas = (float)($data->luas ?? 0);
|
|
||||||
$geom = $conn->real_escape_string(json_encode($data->geom));
|
|
||||||
$query = "UPDATE parsil SET nama='$nama', status='$status', luas=$luas, geom=ST_GeomFromGeoJSON('$geom') WHERE id=$id";
|
|
||||||
} else {
|
|
||||||
$query = "UPDATE parsil SET nama='$nama', status='$status' WHERE id=$id";
|
|
||||||
}
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "Parsil berhasil diupdate."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal update parsil: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "ID tidak diberikan."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdminOrPengelola();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|||||||
@@ -1,18 +1,70 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdminOrPengelola();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$nama = '';
|
||||||
|
$kategori = 'Makan';
|
||||||
|
$jumlah_jiwa = 1;
|
||||||
|
$lat = 0.0;
|
||||||
|
$lng = 0.0;
|
||||||
|
|
||||||
if (!empty($data->lat) && !empty($data->lng)) {
|
if (!empty($_POST)) {
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
$nama = $conn->real_escape_string($_POST['nama'] ?? '');
|
||||||
$kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan');
|
$kategori = $conn->real_escape_string($_POST['kategori_bantuan'] ?? 'Makan');
|
||||||
$jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1);
|
$jumlah_jiwa = (int)($_POST['jumlah_jiwa'] ?? 1);
|
||||||
$lat = (float)$data->lat;
|
$lat = (float)($_POST['lat'] ?? 0);
|
||||||
$lng = (float)$data->lng;
|
$lng = (float)($_POST['lng'] ?? 0);
|
||||||
|
} else {
|
||||||
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
if ($data) {
|
||||||
|
$nama = $conn->real_escape_string($data->nama ?? '');
|
||||||
|
$kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan');
|
||||||
|
$jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1);
|
||||||
|
$lat = (float)($data->lat ?? 0);
|
||||||
|
$lng = (float)($data->lng ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng)
|
if ($lat != 0 && $lng != 0) {
|
||||||
VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng)";
|
$upload_dir = "../../uploads/";
|
||||||
|
if (!file_exists($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto_rumah = null;
|
||||||
|
$foto_kk = null;
|
||||||
|
|
||||||
|
function uploadFile($fileKey, $upload_dir) {
|
||||||
|
if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$fileTmpPath = $_FILES[$fileKey]['tmp_name'];
|
||||||
|
$fileName = $_FILES[$fileKey]['name'];
|
||||||
|
|
||||||
|
$fileNameCmps = explode(".", $fileName);
|
||||||
|
$fileExtension = strtolower(end($fileNameCmps));
|
||||||
|
|
||||||
|
$allowedfileExtensions = array('jpg', 'jpeg', 'png', 'gif');
|
||||||
|
if (in_array($fileExtension, $allowedfileExtensions)) {
|
||||||
|
$newFileName = uniqid() . '_' . preg_replace('/[^a-zA-Z0-9\._-]/', '_', $fileName);
|
||||||
|
$dest_path = $upload_dir . $newFileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($fileTmpPath, $dest_path)) {
|
||||||
|
return $newFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto_rumah = uploadFile('foto_rumah', $upload_dir);
|
||||||
|
$foto_kk = uploadFile('foto_kk', $upload_dir);
|
||||||
|
|
||||||
|
$val_foto_rumah = $foto_rumah ? "'$foto_rumah'" : "NULL";
|
||||||
|
$val_foto_kk = $foto_kk ? "'$foto_kk'" : "NULL";
|
||||||
|
|
||||||
|
$query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng, foto_rumah, foto_kk)
|
||||||
|
VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng, $val_foto_rumah, $val_foto_kk)";
|
||||||
|
|
||||||
if ($conn->query($query)) {
|
if ($conn->query($query)) {
|
||||||
echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Berhasil ditambahkan."]);
|
echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "Berhasil ditambahkan."]);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdminOrPengelola();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ if($result->num_rows > 0) {
|
|||||||
"kategori_bantuan" => $row['kategori_bantuan'],
|
"kategori_bantuan" => $row['kategori_bantuan'],
|
||||||
"jumlah_jiwa" => isset($row['jumlah_jiwa']) ? (int)$row['jumlah_jiwa'] : 1,
|
"jumlah_jiwa" => isset($row['jumlah_jiwa']) ? (int)$row['jumlah_jiwa'] : 1,
|
||||||
"lat" => (float)$row['lat'],
|
"lat" => (float)$row['lat'],
|
||||||
"lng" => (float)$row['lng']
|
"lng" => (float)$row['lng'],
|
||||||
|
"foto_rumah" => $row['foto_rumah'],
|
||||||
|
"foto_kk" => $row['foto_kk']
|
||||||
);
|
);
|
||||||
array_push($arr, $item);
|
array_push($arr, $item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,100 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdminOrPengelola();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$id = 0;
|
||||||
|
$nama = '';
|
||||||
|
$kategori = 'Makan';
|
||||||
|
$jumlah_jiwa = 1;
|
||||||
|
$lat = 0.0;
|
||||||
|
$lng = 0.0;
|
||||||
|
|
||||||
if (!empty($data->id)) {
|
if (!empty($_POST)) {
|
||||||
$id = (int)$data->id;
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
$nama = $conn->real_escape_string($_POST['nama'] ?? '');
|
||||||
$kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan');
|
$kategori = $conn->real_escape_string($_POST['kategori_bantuan'] ?? 'Makan');
|
||||||
$jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1);
|
$jumlah_jiwa = (int)($_POST['jumlah_jiwa'] ?? 1);
|
||||||
$lat = (float)$data->lat;
|
$lat = (float)($_POST['lat'] ?? 0);
|
||||||
$lng = (float)$data->lng;
|
$lng = (float)($_POST['lng'] ?? 0);
|
||||||
|
} else {
|
||||||
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
if ($data) {
|
||||||
|
$id = (int)($data->id ?? 0);
|
||||||
|
$nama = $conn->real_escape_string($data->nama ?? '');
|
||||||
|
$kategori = $conn->real_escape_string($data->kategori_bantuan ?? 'Makan');
|
||||||
|
$jumlah_jiwa = (int)($data->jumlah_jiwa ?? 1);
|
||||||
|
$lat = (float)($data->lat ?? 0);
|
||||||
|
$lng = (float)($data->lng ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$query = "UPDATE penduduk_miskin
|
if ($id > 0) {
|
||||||
SET nama='$nama', kategori_bantuan='$kategori', jumlah_jiwa=$jumlah_jiwa, lat=$lat, lng=$lng
|
// Get current files to delete them if updated
|
||||||
WHERE id=$id";
|
$old_foto_rumah = null;
|
||||||
|
$old_foto_kk = null;
|
||||||
|
$res = $conn->query("SELECT foto_rumah, foto_kk FROM penduduk_miskin WHERE id=$id");
|
||||||
|
if ($res && $row = $res->fetch_assoc()) {
|
||||||
|
$old_foto_rumah = $row['foto_rumah'];
|
||||||
|
$old_foto_kk = $row['foto_kk'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$upload_dir = "../../uploads/";
|
||||||
|
if (!file_exists($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto_rumah = null;
|
||||||
|
$foto_kk = null;
|
||||||
|
|
||||||
|
function uploadFile($fileKey, $upload_dir) {
|
||||||
|
if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$fileTmpPath = $_FILES[$fileKey]['tmp_name'];
|
||||||
|
$fileName = $_FILES[$fileKey]['name'];
|
||||||
|
|
||||||
|
$fileNameCmps = explode(".", $fileName);
|
||||||
|
$fileExtension = strtolower(end($fileNameCmps));
|
||||||
|
|
||||||
|
$allowedfileExtensions = array('jpg', 'jpeg', 'png', 'gif');
|
||||||
|
if (in_array($fileExtension, $allowedfileExtensions)) {
|
||||||
|
$newFileName = uniqid() . '_' . preg_replace('/[^a-zA-Z0-9\._-]/', '_', $fileName);
|
||||||
|
$dest_path = $upload_dir . $newFileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($fileTmpPath, $dest_path)) {
|
||||||
|
return $newFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$foto_rumah = uploadFile('foto_rumah', $upload_dir);
|
||||||
|
$foto_kk = uploadFile('foto_kk', $upload_dir);
|
||||||
|
|
||||||
|
// Build update fields
|
||||||
|
$update_fields = [
|
||||||
|
"nama='$nama'",
|
||||||
|
"kategori_bantuan='$kategori'",
|
||||||
|
"jumlah_jiwa=$jumlah_jiwa",
|
||||||
|
"lat=$lat",
|
||||||
|
"lng=$lng"
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($foto_rumah !== null) {
|
||||||
|
$update_fields[] = "foto_rumah='$foto_rumah'";
|
||||||
|
if ($old_foto_rumah && file_exists($upload_dir . $old_foto_rumah)) {
|
||||||
|
unlink($upload_dir . $old_foto_rumah);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($foto_kk !== null) {
|
||||||
|
$update_fields[] = "foto_kk='$foto_kk'";
|
||||||
|
if ($old_foto_kk && file_exists($upload_dir . $old_foto_kk)) {
|
||||||
|
unlink($upload_dir . $old_foto_kk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "UPDATE penduduk_miskin SET " . implode(", ", $update_fields) . " WHERE id=$id";
|
||||||
|
|
||||||
if ($conn->query($query)) {
|
if ($conn->query($query)) {
|
||||||
echo json_encode(["status" => "success", "message" => "Update berhasil."]);
|
echo json_encode(["status" => "success", "message" => "Update berhasil."]);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
include_once '../db_connect.php';
|
include_once '../db_connect.php';
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
$data = json_decode(file_get_contents("php://input"));
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->lat) && !empty($data->lng)) {
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
|
||||||
$no_wa = $conn->real_escape_string($data->no_wa ?? '');
|
|
||||||
$is_24_jam = isset($data->is_24_jam) ? (int)$data->is_24_jam : 0;
|
|
||||||
$lat = (float)$data->lat;
|
|
||||||
$lng = (float)$data->lng;
|
|
||||||
|
|
||||||
$query = "INSERT INTO spbu (nama, no_wa, is_24_jam, lat, lng) VALUES ('$nama', '$no_wa', $is_24_jam, $lat, $lng)";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "id" => $conn->insert_id, "message" => "SPBU berhasil ditambahkan."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal menambahkan SPBU: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Data tidak lengkap."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
|
|
||||||
$query = "DELETE FROM spbu WHERE id=$id";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "SPBU berhasil dihapus."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal hapus SPBU: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "ID tidak diberikan."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$query = "SELECT * FROM spbu";
|
|
||||||
$result = $conn->query($query);
|
|
||||||
|
|
||||||
$spbu_arr = array();
|
|
||||||
|
|
||||||
if($result->num_rows > 0) {
|
|
||||||
while($row = $result->fetch_assoc()) {
|
|
||||||
$spbu_item = array(
|
|
||||||
"id" => $row['id'],
|
|
||||||
"nama" => $row['nama'],
|
|
||||||
"no_wa" => $row['no_wa'],
|
|
||||||
"is_24_jam" => (bool)$row['is_24_jam'],
|
|
||||||
"lat" => (float)$row['lat'],
|
|
||||||
"lng" => (float)$row['lng']
|
|
||||||
);
|
|
||||||
array_push($spbu_arr, $spbu_item);
|
|
||||||
}
|
|
||||||
echo json_encode(["status" => "success", "data" => $spbu_arr]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "success", "data" => []]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
header("Content-Type: application/json; charset=UTF-8");
|
|
||||||
include_once '../db_connect.php';
|
|
||||||
|
|
||||||
$data = json_decode(file_get_contents("php://input"));
|
|
||||||
|
|
||||||
if(!empty($data->id) && !empty($data->lat) && !empty($data->lng)) {
|
|
||||||
$id = (int)$data->id;
|
|
||||||
$nama = $conn->real_escape_string($data->nama ?? '');
|
|
||||||
$no_wa = $conn->real_escape_string($data->no_wa ?? '');
|
|
||||||
$is_24_jam = isset($data->is_24_jam) ? (int)$data->is_24_jam : 0;
|
|
||||||
$lat = (float)$data->lat;
|
|
||||||
$lng = (float)$data->lng;
|
|
||||||
|
|
||||||
$query = "UPDATE spbu SET nama='$nama', no_wa='$no_wa', is_24_jam=$is_24_jam, lat=$lat, lng=$lng WHERE id=$id";
|
|
||||||
|
|
||||||
if($conn->query($query)) {
|
|
||||||
echo json_encode(["status" => "success", "message" => "SPBU berhasil diupdate."]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Gagal update SPBU: " . $conn->error]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(["status" => "error", "message" => "Data tidak lengkap."]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
|
require_once '../db_connect.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$username = trim($data['username'] ?? '');
|
||||||
|
$password = trim($data['password'] ?? '');
|
||||||
|
$role = trim($data['role'] ?? 'pengelola');
|
||||||
|
$ibadah_id = !empty($data['ibadah_id']) ? intval($data['ibadah_id']) : null;
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username dan password wajib diisi']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'pengelola') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Role tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah username sudah ada
|
||||||
|
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ?");
|
||||||
|
$stmt->bind_param('s', $username);
|
||||||
|
$stmt->execute();
|
||||||
|
if ($stmt->get_result()->num_rows > 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username sudah digunakan']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_BCRYPT);
|
||||||
|
|
||||||
|
// Insert user baru
|
||||||
|
$stmt = $conn->prepare("INSERT INTO users (username, password, role, ibadah_id) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param('sssi', $username, $hashed_password, $role, $ibadah_id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'User berhasil ditambahkan', 'id' => $stmt->insert_id]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal menambahkan user: ' . $conn->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
|
require_once '../db_connect.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = intval($data['id'] ?? 0);
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id === intval($_SESSION['user_id'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Anda tidak bisa menghapus akun Anda sendiri']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
|
||||||
|
$stmt->bind_param('i', $id);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'User berhasil dihapus']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal menghapus user: ' . $conn->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
|
require_once '../db_connect.php';
|
||||||
|
|
||||||
|
$sql = "SELECT u.id, u.username, u.role, u.ibadah_id, ri.nama as nama_ibadah
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN rumah_ibadah ri ON u.ibadah_id = ri.id
|
||||||
|
ORDER BY u.id DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$arr = array();
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$item = array(
|
||||||
|
"id" => intval($row['id']),
|
||||||
|
"username" => $row['username'],
|
||||||
|
"role" => $row['role'],
|
||||||
|
"ibadah_id" => $row['ibadah_id'] ? intval($row['ibadah_id']) : null,
|
||||||
|
"nama_ibadah" => $row['nama_ibadah'] ?? '-'
|
||||||
|
);
|
||||||
|
array_push($arr, $item);
|
||||||
|
}
|
||||||
|
echo json_encode(["status" => "success", "data" => $arr]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(["status" => "success", "data" => []]);
|
||||||
|
}
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
require_once '../auth_helper.php';
|
||||||
|
requireAdmin();
|
||||||
|
require_once '../db_connect.php';
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
$id = intval($data['id'] ?? 0);
|
||||||
|
$username = trim($data['username'] ?? '');
|
||||||
|
$password = trim($data['password'] ?? '');
|
||||||
|
$role = trim($data['role'] ?? 'pengelola');
|
||||||
|
$ibadah_id = !empty($data['ibadah_id']) ? intval($data['ibadah_id']) : null;
|
||||||
|
|
||||||
|
if (!$id || empty($username)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'pengelola') {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Role tidak valid']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah username sudah ada di user lain
|
||||||
|
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? AND id != ?");
|
||||||
|
$stmt->bind_param('si', $username, $id);
|
||||||
|
$stmt->execute();
|
||||||
|
if ($stmt->get_result()->num_rows > 0) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username sudah digunakan oleh user lain']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($password)) {
|
||||||
|
// Hash new password
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_BCRYPT);
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET username = ?, password = ?, role = ?, ibadah_id = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param('ssssi', $username, $hashed_password, $role, $ibadah_id, $id);
|
||||||
|
} else {
|
||||||
|
// Keep existing password
|
||||||
|
$stmt = $conn->prepare("UPDATE users SET username = ?, role = ?, ibadah_id = ? WHERE id = ?");
|
||||||
|
$stmt->bind_param('sssi', $username, $role, $ibadah_id, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'User berhasil diperbarui']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Gagal memperbarui user: ' . $conn->error]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
@@ -139,6 +139,18 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layer-section-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #6366f1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
border-bottom: 1px dashed #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
.layer-option {
|
.layer-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -190,6 +202,25 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-section-label {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
margin: 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-btn {
|
.sidebar-btn {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@@ -400,9 +431,6 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon-spbu { background: #f0fdf4; }
|
|
||||||
.card-icon-jalan { background: #eff6ff; }
|
|
||||||
.card-icon-parsil { background: #fefce8; }
|
|
||||||
.card-icon-ibadah { background: #fff7ed; }
|
.card-icon-ibadah { background: #fff7ed; }
|
||||||
.card-icon-miskin-out { background: #f0fdf4; }
|
.card-icon-miskin-out { background: #f0fdf4; }
|
||||||
.card-icon-miskin-in { background: #fef2f2; }
|
.card-icon-miskin-in { background: #fef2f2; }
|
||||||
@@ -690,6 +718,7 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
.modal-body input[type="text"],
|
.modal-body input[type="text"],
|
||||||
.modal-body input[type="number"],
|
.modal-body input[type="number"],
|
||||||
.modal-body input[type="date"],
|
.modal-body input[type="date"],
|
||||||
|
.modal-body input[type="password"],
|
||||||
.modal-body select,
|
.modal-body select,
|
||||||
.modal-body textarea {
|
.modal-body textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -836,8 +865,6 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Warna bubble per tipe */
|
/* Warna bubble per tipe */
|
||||||
.emoji-marker .bubble.spbu-24 { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
|
||||||
.emoji-marker .bubble.spbu-not24 { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
|
||||||
.emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); }
|
.emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); }
|
||||||
.emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
.emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
||||||
.emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
.emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); }
|
||||||
@@ -893,3 +920,103 @@ body.right-panel-open .custom-layer-panel {
|
|||||||
0% { opacity: 1; transform: translateY(0) scale(1); }
|
0% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
100% { opacity: 0; transform: translateY(10px) scale(0.95); }
|
100% { opacity: 0; transform: translateY(10px) scale(0.95); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Auth Widget Styling */
|
||||||
|
.auth-widget {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 80px;
|
||||||
|
z-index: 1000;
|
||||||
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.right-panel-open .auth-widget {
|
||||||
|
right: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile-pill {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 24px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 8px 0 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile-name {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e1e2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile-role {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6366f1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-profile-logout {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-profile-logout:hover {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User management table styling */
|
||||||
|
#userManagementModal table th {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-bottom: 1px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#userManagementModal table td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// --- Fitur Autentikasi dan Manajemen User (RBAC) ---
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
window.currentUser = null;
|
||||||
|
|
||||||
|
// Elements
|
||||||
|
const authWidget = document.getElementById('authWidget');
|
||||||
|
const loginModal = document.getElementById('loginModal');
|
||||||
|
const loginUsernameInput = document.getElementById('loginUsername');
|
||||||
|
const loginPasswordInput = document.getElementById('loginPassword');
|
||||||
|
const loginSubmitBtn = document.getElementById('loginSubmitBtn');
|
||||||
|
const loginErrorMsg = document.getElementById('loginErrorMsg');
|
||||||
|
const closeLoginModal = document.getElementById('closeLoginModal');
|
||||||
|
|
||||||
|
// User Management Modal Elements
|
||||||
|
const userManagementModal = document.getElementById('userManagementModal');
|
||||||
|
const closeUserManagementModal = document.getElementById('closeUserManagementModal');
|
||||||
|
const menuUsersBtn = document.getElementById('menuUsers');
|
||||||
|
const userTableBody = document.getElementById('userTableBody');
|
||||||
|
const btnSaveUser = document.getElementById('btnSaveUser');
|
||||||
|
const btnCancelUserEdit = document.getElementById('btnCancelUserEdit');
|
||||||
|
const userFormTitle = document.getElementById('userFormTitle');
|
||||||
|
|
||||||
|
const manageUserId = document.getElementById('manageUserId');
|
||||||
|
const manageUsername = document.getElementById('manageUsername');
|
||||||
|
const managePassword = document.getElementById('managePassword');
|
||||||
|
const manageRole = document.getElementById('manageRole');
|
||||||
|
const manageIbadahId = document.getElementById('manageIbadahId');
|
||||||
|
const manageIbadahGroup = document.getElementById('manageIbadahGroup');
|
||||||
|
|
||||||
|
// Startup Session Check
|
||||||
|
function checkSession() {
|
||||||
|
fetch('api/check_session.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success' && data.isLoggedIn) {
|
||||||
|
window.currentUser = data.data;
|
||||||
|
} else {
|
||||||
|
window.currentUser = null;
|
||||||
|
}
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Session check failed:", err);
|
||||||
|
window.currentUser = null;
|
||||||
|
updateAuthUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI based on logged-in state
|
||||||
|
function updateAuthUI() {
|
||||||
|
if (!authWidget) return;
|
||||||
|
|
||||||
|
if (window.currentUser) {
|
||||||
|
// Logged In state
|
||||||
|
const roleLabel = window.currentUser.role === 'admin' ? 'Admin' : 'Pengelola';
|
||||||
|
const extraLabel = window.currentUser.nama_ibadah ? ` - ${window.currentUser.nama_ibadah}` : '';
|
||||||
|
|
||||||
|
authWidget.innerHTML = `
|
||||||
|
<div class="user-profile-pill">
|
||||||
|
<div class="user-profile-info">
|
||||||
|
<div class="user-profile-name">👤 ${escHtml(window.currentUser.username)}</div>
|
||||||
|
<div class="user-profile-role">${roleLabel}${escHtml(extraLabel)}</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-profile-logout" id="authLogoutBtn" title="Logout">
|
||||||
|
<i class="fas fa-sign-out-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('authLogoutBtn').addEventListener('click', logout);
|
||||||
|
|
||||||
|
// Show user management button in sidebar for Admin
|
||||||
|
const adminDivider = document.getElementById('sidebarAdminDivider');
|
||||||
|
if (window.currentUser.role === 'admin') {
|
||||||
|
if (menuUsersBtn) menuUsersBtn.style.display = 'flex';
|
||||||
|
if (adminDivider) adminDivider.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
if (menuUsersBtn) menuUsersBtn.style.display = 'none';
|
||||||
|
if (adminDivider) adminDivider.style.display = 'none';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Logged Out state
|
||||||
|
authWidget.innerHTML = `
|
||||||
|
<button class="btn-login" id="authLoginBtn"><i class="fas fa-sign-in-alt"></i> Login</button>
|
||||||
|
`;
|
||||||
|
document.getElementById('authLoginBtn').addEventListener('click', showLoginModal);
|
||||||
|
if (menuUsersBtn) menuUsersBtn.style.display = 'none';
|
||||||
|
const adminDivider = document.getElementById('sidebarAdminDivider');
|
||||||
|
if (adminDivider) adminDivider.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAllLayers() {
|
||||||
|
if (typeof loadSpbu === 'function') loadSpbu();
|
||||||
|
if (typeof loadJalan === 'function') loadJalan();
|
||||||
|
if (typeof loadParsil === 'function') loadParsil();
|
||||||
|
if (typeof loadRumahIbadah === 'function') loadRumahIbadah();
|
||||||
|
if (typeof loadPendudukMiskin === 'function') loadPendudukMiskin();
|
||||||
|
if (window.refreshActivePanel) window.refreshActivePanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login Modals logic
|
||||||
|
function showLoginModal() {
|
||||||
|
loginUsernameInput.value = '';
|
||||||
|
loginPasswordInput.value = '';
|
||||||
|
loginErrorMsg.style.display = 'none';
|
||||||
|
loginModal.classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoginModal() {
|
||||||
|
loginModal.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeLoginModal) {
|
||||||
|
closeLoginModal.addEventListener('click', hideLoginModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
loginSubmitBtn.addEventListener('click', function() {
|
||||||
|
const username = loginUsernameInput.value.trim();
|
||||||
|
const password = loginPasswordInput.value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
loginErrorMsg.textContent = 'Username dan password wajib diisi';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('api/login.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
window.currentUser = data.data;
|
||||||
|
hideLoginModal();
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
showToast('Login berhasil', 'success');
|
||||||
|
} else {
|
||||||
|
loginErrorMsg.textContent = data.message || 'Login gagal';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
loginErrorMsg.textContent = 'Koneksi ke server terputus';
|
||||||
|
loginErrorMsg.style.display = 'block';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
fetch('api/logout.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
window.currentUser = null;
|
||||||
|
updateAuthUI();
|
||||||
|
refreshAllLayers();
|
||||||
|
showToast('Logout berhasil', 'success');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
showToast('Gagal logout', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Management Modal Logic
|
||||||
|
if (menuUsersBtn) {
|
||||||
|
menuUsersBtn.addEventListener('click', function() {
|
||||||
|
userManagementModal.classList.add('show');
|
||||||
|
loadUsers();
|
||||||
|
populateIbadahDropdown();
|
||||||
|
resetUserForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeUserManagementModal) {
|
||||||
|
closeUserManagementModal.addEventListener('click', function() {
|
||||||
|
userManagementModal.classList.remove('show');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide ibadah dropdown based on selected role in form
|
||||||
|
if (manageRole) {
|
||||||
|
manageRole.addEventListener('change', function() {
|
||||||
|
if (this.value === 'admin') {
|
||||||
|
manageIbadahGroup.style.display = 'none';
|
||||||
|
manageIbadahId.value = '';
|
||||||
|
} else {
|
||||||
|
manageIbadahGroup.style.display = 'flex';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateIbadahDropdown() {
|
||||||
|
if (!manageIbadahId) return;
|
||||||
|
manageIbadahId.innerHTML = '<option value="">-- Pilih Rumah Ibadah --</option>';
|
||||||
|
if (typeof ibadahDataList !== 'undefined') {
|
||||||
|
ibadahDataList.forEach(ib => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ib.id;
|
||||||
|
opt.textContent = `${ib.jenis || '🕌'} ${ib.nama}`;
|
||||||
|
manageIbadahId.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadUsers() {
|
||||||
|
if (!userTableBody) return;
|
||||||
|
userTableBody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding:15px; color:#aaa;">Memuat user...</td></tr>';
|
||||||
|
|
||||||
|
fetch('api/users/read.php')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
userTableBody.innerHTML = '';
|
||||||
|
if (data.data.length === 0) {
|
||||||
|
userTableBody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding:15px; color:#aaa;">Belum ada user</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.data.forEach(user => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.style.borderBottom = '1px solid #e2e8f0';
|
||||||
|
|
||||||
|
const roleBadge = user.role === 'admin'
|
||||||
|
? '<span class="data-card-badge badge-red">Admin</span>'
|
||||||
|
: '<span class="data-card-badge badge-blue">Pengelola</span>';
|
||||||
|
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td style="padding:10px 12px; font-weight:600;">${escHtml(user.username)}</td>
|
||||||
|
<td style="padding:10px 12px;">${roleBadge}</td>
|
||||||
|
<td style="padding:10px 12px; color:#64748b;">${escHtml(user.nama_ibadah)}</td>
|
||||||
|
<td style="padding:10px 12px; text-align:center; display:flex; gap:6px; justify-content:center;">
|
||||||
|
<button class="btn-card-action btn-card-edit" title="Edit User" onclick="editUser(${JSON.stringify(user).replace(/"/g, '"')})">
|
||||||
|
<i class="fas fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn-card-action btn-card-delete" title="Hapus User" onclick="deleteUser(${user.id})">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
userTableBody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
userTableBody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding:15px; color:#ef4444;">Gagal memuat user</td></tr>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.editUser = function(user) {
|
||||||
|
userFormTitle.textContent = 'Edit Pengguna';
|
||||||
|
manageUserId.value = user.id;
|
||||||
|
manageUsername.value = user.username;
|
||||||
|
managePassword.value = ''; // Password cleared in edit
|
||||||
|
manageRole.value = user.role;
|
||||||
|
|
||||||
|
if (user.role === 'admin') {
|
||||||
|
manageIbadahGroup.style.display = 'none';
|
||||||
|
manageIbadahId.value = '';
|
||||||
|
} else {
|
||||||
|
manageIbadahGroup.style.display = 'flex';
|
||||||
|
manageIbadahId.value = user.ibadah_id || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
btnCancelUserEdit.style.display = 'inline-block';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.deleteUser = function(id) {
|
||||||
|
if (confirm('Apakah Anda yakin ingin menghapus user ini?')) {
|
||||||
|
fetch('api/users/delete.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showToast(data.message, 'success');
|
||||||
|
loadUsers();
|
||||||
|
resetUserForm();
|
||||||
|
} else {
|
||||||
|
showToast(data.message || 'Gagal menghapus user', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
showToast('Koneksi terputus', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
btnSaveUser.addEventListener('click', function() {
|
||||||
|
const id = manageUserId.value;
|
||||||
|
const username = manageUsername.value.trim();
|
||||||
|
const password = managePassword.value;
|
||||||
|
const role = manageRole.value;
|
||||||
|
const ibadah_id = manageIbadahId.value;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
alert('Username wajib diisi');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id && !password) {
|
||||||
|
alert('Password wajib diisi untuk user baru');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = { username, role, ibadah_id };
|
||||||
|
if (id) payload.id = id;
|
||||||
|
if (password) payload.password = password;
|
||||||
|
|
||||||
|
const endpoint = id ? 'api/users/update.php' : 'api/users/create.php';
|
||||||
|
|
||||||
|
fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
showToast(data.message, 'success');
|
||||||
|
loadUsers();
|
||||||
|
resetUserForm();
|
||||||
|
} else {
|
||||||
|
alert(data.message || 'Gagal menyimpan user');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert('Koneksi terputus');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCancelUserEdit.addEventListener('click', resetUserForm);
|
||||||
|
|
||||||
|
function resetUserForm() {
|
||||||
|
userFormTitle.textContent = 'Tambah User Baru';
|
||||||
|
manageUserId.value = '';
|
||||||
|
manageUsername.value = '';
|
||||||
|
managePassword.value = '';
|
||||||
|
manageRole.value = 'pengelola';
|
||||||
|
manageIbadahGroup.style.display = 'flex';
|
||||||
|
manageIbadahId.value = '';
|
||||||
|
btnCancelUserEdit.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function escHtml(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast Notification helper
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
if (typeof window.showToast === 'function') {
|
||||||
|
window.showToast(message, type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fallback alert
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run session check on initialization
|
||||||
|
checkSession();
|
||||||
|
})();
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
// --- Setup Leaflet Draw ---
|
|
||||||
|
|
||||||
// Layer terpisah untuk hasil gambar sementara sebelum disimpan
|
|
||||||
const drawnItems = new L.FeatureGroup();
|
|
||||||
map.addLayer(drawnItems);
|
|
||||||
|
|
||||||
const drawControl = new L.Control.Draw({
|
|
||||||
draw: {
|
|
||||||
polygon: {
|
|
||||||
allowIntersection: false,
|
|
||||||
showArea: true
|
|
||||||
},
|
|
||||||
polyline: {
|
|
||||||
metric: true
|
|
||||||
},
|
|
||||||
circle: false,
|
|
||||||
circlemarker: false,
|
|
||||||
marker: false, // Marker default dimatikan, kita pakai klik peta untuk SPBU
|
|
||||||
rectangle: false
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
featureGroup: drawnItems, // Kita tidak mengedit di drawnItems, karena data aslinya di jalanLayer/parsilLayer
|
|
||||||
edit: false,
|
|
||||||
remove: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Hapus map.addControl(drawControl); agar tidak tampil UI bawaan LeafletJS
|
|
||||||
|
|
||||||
|
|
||||||
// Flag untuk menghindari munculnya form SPBU saat sedang menggambar
|
|
||||||
map.on(L.Draw.Event.DRAWSTART, function (e) {
|
|
||||||
isDrawingMode = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on(L.Draw.Event.DRAWSTOP, function (e) {
|
|
||||||
setTimeout(() => {
|
|
||||||
isDrawingMode = false;
|
|
||||||
window.currentDrawMode = null;
|
|
||||||
window.activeDrawHandler = null;
|
|
||||||
window.deactivateAddMode();
|
|
||||||
}, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.activateDraw = function(type) {
|
|
||||||
console.log("-> activateDraw dipanggil untuk tipe:", type);
|
|
||||||
|
|
||||||
// Toggle: jika mode yang sama ditekan lagi, batalkan
|
|
||||||
if (window.currentDrawMode === type) {
|
|
||||||
console.log("Mode", type, "sudah aktif, membatalkan...");
|
|
||||||
window.deactivateAddMode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.deactivateAddMode();
|
|
||||||
window.currentDrawMode = type;
|
|
||||||
console.log("Mengaktifkan mode menggambar:", type);
|
|
||||||
|
|
||||||
if (type === 'polyline') {
|
|
||||||
const handler = new L.Draw.Polyline(map, drawControl.options.draw.polyline);
|
|
||||||
handler.enable();
|
|
||||||
window.activeDrawHandler = handler;
|
|
||||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Jalan';
|
|
||||||
} else if (type === 'polygon') {
|
|
||||||
const handler = new L.Draw.Polygon(map, drawControl.options.draw.polygon);
|
|
||||||
handler.enable();
|
|
||||||
window.activeDrawHandler = handler;
|
|
||||||
if (window.cursorTooltip) window.cursorTooltip.textContent = 'Klik untuk menggambar Parsil';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
map.on(L.Draw.Event.CREATED, function (e) {
|
|
||||||
const type = e.layerType;
|
|
||||||
const layer = e.layer;
|
|
||||||
|
|
||||||
// Convert layer to GeoJSON geometry
|
|
||||||
const geoJson = layer.toGeoJSON().geometry;
|
|
||||||
const geoJsonStr = JSON.stringify(geoJson);
|
|
||||||
|
|
||||||
if (type === 'polyline') {
|
|
||||||
let length = 0;
|
|
||||||
const latlngs = layer.getLatLngs();
|
|
||||||
for (let i = 0; i < latlngs.length - 1; i++) {
|
|
||||||
length += latlngs[i].distanceTo(latlngs[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gunakan Modal alih-alih prompt
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Jalan</label>
|
|
||||||
<input type="text" id="modalJalanNama" placeholder="Nama Jalan">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Status</label>
|
|
||||||
<select id="modalJalanStatus">
|
|
||||||
<option value="Nasional">Nasional</option>
|
|
||||||
<option value="Provinsi">Provinsi</option>
|
|
||||||
<option value="Kabupaten" selected>Kabupaten</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Panjang: ${length.toFixed(2)} m</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
openModal("Tambah Jalan Baru", bodyHTML, function() {
|
|
||||||
window.saveNewJalan(geoJsonStr, length, 'modalJalanNama', 'modalJalanStatus');
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (type === 'polygon') {
|
|
||||||
const latlngs = layer.getLatLngs()[0];
|
|
||||||
let area = 0;
|
|
||||||
if (L.GeometryUtil && L.GeometryUtil.geodesicArea) {
|
|
||||||
area = L.GeometryUtil.geodesicArea(latlngs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Pemilik/Area</label>
|
|
||||||
<input type="text" id="modalParsilNama" placeholder="Contoh: Budi atau Sawah A">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Status Tanah</label>
|
|
||||||
<select id="modalParsilStatus">
|
|
||||||
<option value="SHM" selected>SHM</option>
|
|
||||||
<option value="HGB">HGB</option>
|
|
||||||
<option value="HGU">HGU</option>
|
|
||||||
<option value="HP">HP</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Luas: ${area.toFixed(2)} m²</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
openModal("Tambah Parsil Tanah", bodyHTML, function() {
|
|
||||||
window.saveNewParsil(geoJsonStr, area, 'modalParsilNama', 'modalParsilStatus');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
// --- Fitur Jalan ---
|
|
||||||
|
|
||||||
const jalanColors = {
|
|
||||||
'Nasional': '#ff0000', // Merah
|
|
||||||
'Provinsi': '#0000ff', // Biru
|
|
||||||
'Kabupaten': '#00ff00' // Hijau
|
|
||||||
};
|
|
||||||
|
|
||||||
function loadJalan() {
|
|
||||||
jalanLayer.clearLayers();
|
|
||||||
fetch('api/jalan/read.php')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'success' && data.data) {
|
|
||||||
data.data.forEach(item => {
|
|
||||||
addJalanToMap(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addJalanToMap(item) {
|
|
||||||
// geom adalah GeoJSON {type: "LineString", coordinates: [[lng, lat], ...]}
|
|
||||||
const latlngs = item.geom.coordinates.map(coord => [coord[1], coord[0]]);
|
|
||||||
|
|
||||||
const polyline = L.polyline(latlngs, {
|
|
||||||
color: jalanColors[item.status] || '#3388ff',
|
|
||||||
weight: 5
|
|
||||||
});
|
|
||||||
|
|
||||||
polyline.jalanData = item;
|
|
||||||
|
|
||||||
// Tampilkan label nama jalan sejajar dengan garis (diagonal)
|
|
||||||
polyline.setText(item.nama, {
|
|
||||||
center: true,
|
|
||||||
offset: 0,
|
|
||||||
attributes: {
|
|
||||||
fill: '#000000',
|
|
||||||
'font-weight': 'bold',
|
|
||||||
'font-size': '14px',
|
|
||||||
'dy': '7'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hitung popupContent sekali saat jalan dibuat
|
|
||||||
const d = item;
|
|
||||||
const popupContent = `
|
|
||||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
|
||||||
<h4 style="margin:0 0 5px 0;">Jalan ${d.nama}</h4>
|
|
||||||
<p style="margin: 0 0 5px 0;"><b>Status:</b> ${d.status}</p>
|
|
||||||
<p style="margin: 0 0 10px 0;"><b>Panjang:</b> ${d.panjang.toFixed(2)} m</p>
|
|
||||||
<div style="display:flex; gap:5px;">
|
|
||||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditJalanModal(${d.id})">Edit</button>
|
|
||||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteJalan(${d.id})">Hapus</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
polyline.bindPopup(popupContent);
|
|
||||||
|
|
||||||
jalanLayer.addLayer(polyline);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.openEditJalanModal = function(id) {
|
|
||||||
let d = null;
|
|
||||||
jalanLayer.eachLayer(function(layer) {
|
|
||||||
if (layer.jalanData && layer.jalanData.id == id) d = layer.jalanData;
|
|
||||||
});
|
|
||||||
if (!d) return;
|
|
||||||
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Jalan</label>
|
|
||||||
<input type="text" id="editJalanNama" value="${d.nama}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Status</label>
|
|
||||||
<select id="editJalanStatus">
|
|
||||||
<option value="Nasional" ${d.status === 'Nasional' ? 'selected' : ''}>Nasional</option>
|
|
||||||
<option value="Provinsi" ${d.status === 'Provinsi' ? 'selected' : ''}>Provinsi</option>
|
|
||||||
<option value="Kabupaten" ${d.status === 'Kabupaten' ? 'selected' : ''}>Kabupaten</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Panjang: ${d.panjang.toFixed(2)} m</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
map.closePopup();
|
|
||||||
openModal("Edit Jalan", bodyHTML, function() {
|
|
||||||
window.saveEditJalan(d.id, 'editJalanNama', 'editJalanStatus');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.saveEditJalan = function(id, namaId, statusId) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
const status = document.getElementById(statusId).value;
|
|
||||||
|
|
||||||
fetch('api/jalan/update.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id, nama, status })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
closeModal();
|
|
||||||
loadJalan();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.deleteJalan = function(id) {
|
|
||||||
openConfirmModal("Yakin hapus jalan ini?", function() {
|
|
||||||
fetch('api/jalan/delete.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
loadJalan();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.saveNewJalan = function(geoJsonStr, panjang, namaId, statusId) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
if (!nama) {
|
|
||||||
alert("Nama jalan harus diisi!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = document.getElementById(statusId).value;
|
|
||||||
|
|
||||||
const geom = JSON.parse(geoJsonStr);
|
|
||||||
|
|
||||||
fetch('api/jalan/create.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ nama, status, panjang, geom })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
closeModal();
|
|
||||||
loadJalan();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial Load
|
|
||||||
loadJalan();
|
|
||||||
@@ -95,7 +95,9 @@ function addIbadahMarker(item) {
|
|||||||
|
|
||||||
if (isNaN(lat) || isNaN(lng)) return;
|
if (isNaN(lat) || isNaN(lng)) return;
|
||||||
|
|
||||||
const marker = L.marker([lat, lng], { icon: makeIbadahIcon(item.jenis), draggable: true });
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
|
|
||||||
|
const marker = L.marker([lat, lng], { icon: makeIbadahIcon(item.jenis), draggable: isAdmin });
|
||||||
|
|
||||||
// Lingkaran radius
|
// Lingkaran radius
|
||||||
const circle = L.circle([lat, lng], {
|
const circle = L.circle([lat, lng], {
|
||||||
@@ -107,7 +109,7 @@ function addIbadahMarker(item) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
circle.on('mousemove', function(e) {
|
circle.on('mousemove', function(e) {
|
||||||
if (isResizing) return;
|
if (!isAdmin || isResizing) return;
|
||||||
const center = circle.getLatLng();
|
const center = circle.getLatLng();
|
||||||
const radius = circle.getRadius();
|
const radius = circle.getRadius();
|
||||||
const dist = center.distanceTo(e.latlng);
|
const dist = center.distanceTo(e.latlng);
|
||||||
@@ -125,7 +127,7 @@ function addIbadahMarker(item) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
circle.on('mousedown', function(e) {
|
circle.on('mousedown', function(e) {
|
||||||
if (circle.nearEdge) {
|
if (isAdmin && circle.nearEdge) {
|
||||||
map.dragging.disable();
|
map.dragging.disable();
|
||||||
isResizing = true;
|
isResizing = true;
|
||||||
resizingCircle = circle;
|
resizingCircle = circle;
|
||||||
@@ -139,16 +141,22 @@ function addIbadahMarker(item) {
|
|||||||
|
|
||||||
const d = item;
|
const d = item;
|
||||||
const emot = IBADAH_EMOJI_MAP[d.jenis] || '🕌';
|
const emot = IBADAH_EMOJI_MAP[d.jenis] || '🕌';
|
||||||
|
let actionButtons = '';
|
||||||
|
if (isAdmin) {
|
||||||
|
actionButtons = `
|
||||||
|
<div style="display:flex; gap:5px;">
|
||||||
|
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditIbadahModal(${d.id})">Edit</button>
|
||||||
|
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteIbadah(${d.id})">Hapus</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
const popupContent = `
|
const popupContent = `
|
||||||
<div style="font-family: Arial, sans-serif; min-width: 160px;">
|
<div style="font-family: Arial, sans-serif; min-width: 160px;">
|
||||||
<h4 style="margin:0 0 5px 0;">${emot} ${d.nama}</h4>
|
<h4 style="margin:0 0 5px 0;">${emot} ${d.nama}</h4>
|
||||||
<p style="margin: 0 0 3px 0;"><b>Jenis:</b> ${d.jenis || 'Masjid'}</p>
|
<p style="margin: 0 0 3px 0;"><b>Jenis:</b> ${d.jenis || 'Masjid'}</p>
|
||||||
<p style="margin: 0 0 3px 0;"><b>Alamat:</b> ${d.alamat}</p>
|
<p style="margin: 0 0 3px 0;"><b>Alamat:</b> ${d.alamat}</p>
|
||||||
<p style="margin: 0 0 10px 0;"><b>Radius:</b> ${d.radius} m</p>
|
<p style="margin: 0 0 10px 0;"><b>Radius:</b> ${d.radius} m</p>
|
||||||
<div style="display:flex; gap:5px;">
|
${actionButtons}
|
||||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditIbadahModal(${d.id})">Edit</button>
|
|
||||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteIbadah(${d.id})">Hapus</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
marker.bindPopup(popupContent);
|
marker.bindPopup(popupContent);
|
||||||
@@ -168,7 +176,9 @@ function addIbadahMarker(item) {
|
|||||||
|
|
||||||
// Konteks Menu untuk Tambah Penduduk Miskin (klik kanan peta)
|
// Konteks Menu untuk Tambah Penduduk Miskin (klik kanan peta)
|
||||||
map.on('contextmenu', function(e) {
|
map.on('contextmenu', function(e) {
|
||||||
if (isDrawingMode) return;
|
|
||||||
|
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
|
||||||
|
if (!canManage) return;
|
||||||
|
|
||||||
const popupContent = `
|
const popupContent = `
|
||||||
<div class="popup-form">
|
<div class="popup-form">
|
||||||
@@ -181,8 +191,6 @@ map.on('contextmenu', function(e) {
|
|||||||
|
|
||||||
// Map Click -> Form Add Rumah Ibadah
|
// Map Click -> Form Add Rumah Ibadah
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
if (isDrawingMode) return;
|
|
||||||
|
|
||||||
if (window.currentAddMode === 'rumah_ibadah') {
|
if (window.currentAddMode === 'rumah_ibadah') {
|
||||||
const lat = e.latlng.lat;
|
const lat = e.latlng.lat;
|
||||||
const lng = e.latlng.lng;
|
const lng = e.latlng.lng;
|
||||||
@@ -389,19 +397,55 @@ function addMiskinMarker(item) {
|
|||||||
const lng = parseFloat(item.lng);
|
const lng = parseFloat(item.lng);
|
||||||
if (isNaN(lat) || isNaN(lng)) return;
|
if (isNaN(lat) || isNaN(lng)) return;
|
||||||
|
|
||||||
const marker = L.marker([lat, lng], { icon: makeMiskinIcon(false), draggable: true });
|
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
|
||||||
|
|
||||||
|
const marker = L.marker([lat, lng], { icon: makeMiskinIcon(false), draggable: canManage });
|
||||||
marker.miskinData = item;
|
marker.miskinData = item;
|
||||||
|
|
||||||
const d = item;
|
const d = item;
|
||||||
|
let actionButtons = '';
|
||||||
|
if (canManage) {
|
||||||
|
actionButtons = `
|
||||||
|
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditMiskinModal(${d.id})">Edit</button>
|
||||||
|
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteMiskin(${d.id})">Hapus</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let photoLinksHtml = '';
|
||||||
|
if (d.foto_rumah) {
|
||||||
|
photoLinksHtml += `
|
||||||
|
<div style="margin: 5px 0;">
|
||||||
|
<b style="font-size:11px;">Foto Rumah:</b><br>
|
||||||
|
<a href="/poverty/uploads/${d.foto_rumah}" target="_blank">
|
||||||
|
<img src="/poverty/uploads/${d.foto_rumah}" alt="Foto Rumah"
|
||||||
|
style="max-width:150px; max-height:100px; border-radius:4px; margin-top:3px; border:1px solid #ddd; object-fit:cover; cursor:pointer;"
|
||||||
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"/>
|
||||||
|
<span style="display:none; font-size:11px; color:#ef4444;">❌ Foto tidak dapat dimuat</span>
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (d.foto_kk) {
|
||||||
|
photoLinksHtml += `
|
||||||
|
<div style="margin: 5px 0;">
|
||||||
|
<b style="font-size:11px;">Foto KK:</b><br>
|
||||||
|
<a href="/poverty/uploads/${d.foto_kk}" target="_blank">
|
||||||
|
<img src="/poverty/uploads/${d.foto_kk}" alt="Foto KK"
|
||||||
|
style="max-width:150px; max-height:100px; border-radius:4px; margin-top:3px; border:1px solid #ddd; object-fit:cover; cursor:pointer;"
|
||||||
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"/>
|
||||||
|
<span style="display:none; font-size:11px; color:#ef4444;">❌ Foto tidak dapat dimuat</span>
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
const popupContent = `
|
const popupContent = `
|
||||||
<div style="font-family: Arial, sans-serif; min-width: 165px;">
|
<div style="font-family: Arial, sans-serif; min-width: 165px;">
|
||||||
<h4 style="margin:0 0 5px 0;">🏠 ${d.nama}</h4>
|
<h4 style="margin:0 0 5px 0;">🏠 ${d.nama}</h4>
|
||||||
<p style="margin: 0 0 3px 0;"><b>Bantuan:</b> ${d.kategori_bantuan}</p>
|
<p style="margin: 0 0 3px 0;"><b>Bantuan:</b> ${d.kategori_bantuan}</p>
|
||||||
<p style="margin: 0 0 10px 0;"><b>Jiwa:</b> ${d.jumlah_jiwa || '-'}</p>
|
<p style="margin: 0 0 5px 0;"><b>Jiwa:</b> ${d.jumlah_jiwa || '-'}</p>
|
||||||
<div style="display:flex; gap:5px; flex-wrap:wrap;">
|
${photoLinksHtml}
|
||||||
|
<div style="display:flex; gap:5px; flex-wrap:wrap; margin-top:8px;">
|
||||||
<button style="padding:4px 8px; background:#6366f1; color:white; border:none; border-radius:3px; cursor:pointer; font-size:11px;" onclick="openLogBantuan(${d.id}, '${d.nama.replace(/'/g, "\\'")}');">📋 Log Bantuan</button>
|
<button style="padding:4px 8px; background:#6366f1; color:white; border:none; border-radius:3px; cursor:pointer; font-size:11px;" onclick="openLogBantuan(${d.id}, '${d.nama.replace(/'/g, "\\'")}');">📋 Log Bantuan</button>
|
||||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditMiskinModal(${d.id})">Edit</button>
|
${actionButtons}
|
||||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteMiskin(${d.id})">Hapus</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -445,6 +489,14 @@ window.formAddMiskin = function(lat, lng) {
|
|||||||
<label>Jumlah Jiwa</label>
|
<label>Jumlah Jiwa</label>
|
||||||
<input type="number" id="miskinJiwa" value="1" min="1">
|
<input type="number" id="miskinJiwa" value="1" min="1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Foto Rumah</label>
|
||||||
|
<input type="file" id="miskinFotoRumah" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Foto Kartu Keluarga (KK)</label>
|
||||||
|
<input type="file" id="miskinFotoKK" accept="image/*">
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
openModal("Tambah Penduduk Miskin", bodyHTML, function() {
|
openModal("Tambah Penduduk Miskin", bodyHTML, function() {
|
||||||
window.saveNewMiskin(lat, lng);
|
window.saveNewMiskin(lat, lng);
|
||||||
@@ -455,13 +507,23 @@ window.saveNewMiskin = function(lat, lng) {
|
|||||||
const nama = document.getElementById('miskinNama').value;
|
const nama = document.getElementById('miskinNama').value;
|
||||||
const kategori_bantuan = document.getElementById('miskinKategori').value;
|
const kategori_bantuan = document.getElementById('miskinKategori').value;
|
||||||
const jumlah_jiwa = document.getElementById('miskinJiwa').value;
|
const jumlah_jiwa = document.getElementById('miskinJiwa').value;
|
||||||
|
const fotoRumahInput = document.getElementById('miskinFotoRumah');
|
||||||
|
const fotoKKInput = document.getElementById('miskinFotoKK');
|
||||||
|
|
||||||
if (!nama) { alert('Nama harus diisi!'); return; }
|
if (!nama) { alert('Nama harus diisi!'); return; }
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('nama', nama);
|
||||||
|
formData.append('kategori_bantuan', kategori_bantuan);
|
||||||
|
formData.append('jumlah_jiwa', jumlah_jiwa);
|
||||||
|
formData.append('lat', lat);
|
||||||
|
formData.append('lng', lng);
|
||||||
|
if (fotoRumahInput.files[0]) formData.append('foto_rumah', fotoRumahInput.files[0]);
|
||||||
|
if (fotoKKInput.files[0]) formData.append('foto_kk', fotoKKInput.files[0]);
|
||||||
|
|
||||||
fetch('api/penduduk_miskin/create.php', {
|
fetch('api/penduduk_miskin/create.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: formData
|
||||||
body: JSON.stringify({ nama, kategori_bantuan, jumlah_jiwa, lat, lng })
|
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -495,6 +557,32 @@ window.openEditMiskinModal = function(id) {
|
|||||||
<label>Jumlah Jiwa</label>
|
<label>Jumlah Jiwa</label>
|
||||||
<input type="number" id="editMiskinJiwa" value="${d.jumlah_jiwa || 1}" min="1">
|
<input type="number" id="editMiskinJiwa" value="${d.jumlah_jiwa || 1}" min="1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Foto Rumah</label>
|
||||||
|
<input type="file" id="editMiskinFotoRumah" accept="image/*">
|
||||||
|
${d.foto_rumah ? `
|
||||||
|
<div style="margin-top:5px;">
|
||||||
|
<a href="/poverty/uploads/${d.foto_rumah}" target="_blank">
|
||||||
|
<img src="/poverty/uploads/${d.foto_rumah}" alt="Foto Rumah" style="max-width:120px; max-height:80px; border-radius:4px; border:1px solid #ddd; object-fit:cover;"
|
||||||
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"/>
|
||||||
|
<span style="display:none; font-size:11px; color:#ef4444;">❌ File gambar rusak atau tidak ditemukan</span>
|
||||||
|
</a>
|
||||||
|
<div style="font-size:11px;color:#888;margin-top:2px;">Upload baru untuk mengganti</div>
|
||||||
|
</div>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Foto Kartu Keluarga (KK)</label>
|
||||||
|
<input type="file" id="editMiskinFotoKK" accept="image/*">
|
||||||
|
${d.foto_kk ? `
|
||||||
|
<div style="margin-top:5px;">
|
||||||
|
<a href="/poverty/uploads/${d.foto_kk}" target="_blank">
|
||||||
|
<img src="/poverty/uploads/${d.foto_kk}" alt="Foto KK" style="max-width:120px; max-height:80px; border-radius:4px; border:1px solid #ddd; object-fit:cover;"
|
||||||
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='inline';"/>
|
||||||
|
<span style="display:none; font-size:11px; color:#ef4444;">❌ File gambar rusak atau tidak ditemukan</span>
|
||||||
|
</a>
|
||||||
|
<div style="font-size:11px;color:#888;margin-top:2px;">Upload baru untuk mengganti</div>
|
||||||
|
</div>` : ''}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
map.closePopup();
|
map.closePopup();
|
||||||
openModal("Edit Penduduk Miskin", bodyHTML, function() {
|
openModal("Edit Penduduk Miskin", bodyHTML, function() {
|
||||||
@@ -506,10 +594,22 @@ window.saveEditMiskin = function(id, lat, lng, namaId, kategoriId, jiwaId) {
|
|||||||
const nama = document.getElementById(namaId).value;
|
const nama = document.getElementById(namaId).value;
|
||||||
const kategori_bantuan = document.getElementById(kategoriId).value;
|
const kategori_bantuan = document.getElementById(kategoriId).value;
|
||||||
const jumlah_jiwa = jiwaId ? document.getElementById(jiwaId).value : 1;
|
const jumlah_jiwa = jiwaId ? document.getElementById(jiwaId).value : 1;
|
||||||
|
const fotoRumahInput = document.getElementById('editMiskinFotoRumah');
|
||||||
|
const fotoKKInput = document.getElementById('editMiskinFotoKK');
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
formData.append('nama', nama);
|
||||||
|
formData.append('kategori_bantuan', kategori_bantuan);
|
||||||
|
formData.append('jumlah_jiwa', jumlah_jiwa);
|
||||||
|
formData.append('lat', lat);
|
||||||
|
formData.append('lng', lng);
|
||||||
|
if (fotoRumahInput.files[0]) formData.append('foto_rumah', fotoRumahInput.files[0]);
|
||||||
|
if (fotoKKInput.files[0]) formData.append('foto_kk', fotoKKInput.files[0]);
|
||||||
|
|
||||||
fetch('api/penduduk_miskin/update.php', {
|
fetch('api/penduduk_miskin/update.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: formData
|
||||||
body: JSON.stringify({ id, nama, kategori_bantuan, jumlah_jiwa, lat, lng })
|
|
||||||
}).then(res => res.json()).then(data => {
|
}).then(res => res.json()).then(data => {
|
||||||
if(data.status === 'success') { closeModal(); loadPendudukMiskin(); }
|
if(data.status === 'success') { closeModal(); loadPendudukMiskin(); }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
// --- Fitur Parsil ---
|
|
||||||
|
|
||||||
const parsilColors = {
|
|
||||||
'SHM': '#28a745', // Hijau
|
|
||||||
'HGB': '#17a2b8', // Biru Muda
|
|
||||||
'HGU': '#ffc107', // Kuning
|
|
||||||
'HP': '#fd7e14' // Oranye
|
|
||||||
};
|
|
||||||
|
|
||||||
function loadParsil() {
|
|
||||||
parsilLayer.clearLayers();
|
|
||||||
fetch('api/parsil/read.php')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'success' && data.data) {
|
|
||||||
data.data.forEach(item => {
|
|
||||||
addParsilToMap(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addParsilToMap(item) {
|
|
||||||
// geom adalah GeoJSON Polygon
|
|
||||||
// coordinates pada Polygon formatnya: [[[lng, lat], [lng, lat], ...]]
|
|
||||||
// Leaflet Polygon butuh array of [lat, lng]
|
|
||||||
const latlngs = item.geom.coordinates[0].map(coord => [coord[1], coord[0]]);
|
|
||||||
|
|
||||||
const polygon = L.polygon(latlngs, {
|
|
||||||
color: parsilColors[item.status] || '#3388ff',
|
|
||||||
fillColor: parsilColors[item.status] || '#3388ff',
|
|
||||||
fillOpacity: 0.5,
|
|
||||||
weight: 2
|
|
||||||
});
|
|
||||||
|
|
||||||
polygon.parsilData = item;
|
|
||||||
|
|
||||||
const d = item;
|
|
||||||
const popupContent = `
|
|
||||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
|
||||||
<h4 style="margin:0 0 5px 0;">Parsil ${d.nama || ''}</h4>
|
|
||||||
<p style="margin: 0 0 5px 0;"><b>Status:</b> ${d.status}</p>
|
|
||||||
<p style="margin: 0 0 10px 0;"><b>Luas:</b> ${d.luas.toFixed(2)} m²</p>
|
|
||||||
<div style="display:flex; gap:5px;">
|
|
||||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditParsilModal(${d.id})">Edit</button>
|
|
||||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteParsil(${d.id})">Hapus</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
polygon.bindPopup(popupContent);
|
|
||||||
|
|
||||||
parsilLayer.addLayer(polygon);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.openEditParsilModal = function(id) {
|
|
||||||
let d = null;
|
|
||||||
parsilLayer.eachLayer(function(layer) {
|
|
||||||
if (layer.parsilData && layer.parsilData.id == id) d = layer.parsilData;
|
|
||||||
});
|
|
||||||
if (!d) return;
|
|
||||||
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Pemilik/Area</label>
|
|
||||||
<input type="text" id="editParsilNama" value="${d.nama || ''}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Status</label>
|
|
||||||
<select id="editParsilStatus">
|
|
||||||
<option value="SHM" ${d.status === 'SHM' ? 'selected' : ''}>SHM</option>
|
|
||||||
<option value="HGB" ${d.status === 'HGB' ? 'selected' : ''}>HGB</option>
|
|
||||||
<option value="HGU" ${d.status === 'HGU' ? 'selected' : ''}>HGU</option>
|
|
||||||
<option value="HP" ${d.status === 'HP' ? 'selected' : ''}>HP</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Luas: ${d.luas.toFixed(2)} m²</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
map.closePopup();
|
|
||||||
openModal("Edit Parsil Tanah", bodyHTML, function() {
|
|
||||||
window.saveEditParsil(d.id, 'editParsilNama', 'editParsilStatus');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.saveEditParsil = function(id, namaId, statusId) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
const status = document.getElementById(statusId).value;
|
|
||||||
|
|
||||||
fetch('api/parsil/update.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id, nama, status })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
closeModal();
|
|
||||||
loadParsil();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.deleteParsil = function(id) {
|
|
||||||
openConfirmModal("Yakin hapus parsil ini?", function() {
|
|
||||||
fetch('api/parsil/delete.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
loadParsil();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.saveNewParsil = function(geoJsonStr, luas, namaId, statusId) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
const status = document.getElementById(statusId).value;
|
|
||||||
|
|
||||||
const geom = JSON.parse(geoJsonStr);
|
|
||||||
|
|
||||||
fetch('api/parsil/create.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ nama, status, luas, geom })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
closeModal();
|
|
||||||
loadParsil();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial Load
|
|
||||||
loadParsil();
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
// --- Fitur SPBU ---
|
|
||||||
|
|
||||||
// Emoji Bubble Icon builder
|
|
||||||
function makeSpbuIcon(is24) {
|
|
||||||
const cls = is24 ? 'spbu-24' : 'spbu-not24';
|
|
||||||
return L.divIcon({
|
|
||||||
className: '',
|
|
||||||
html: `<div class="emoji-marker"><div class="bubble ${cls}"><span>⛽</span></div></div>`,
|
|
||||||
iconSize: [38, 38],
|
|
||||||
iconAnchor: [19, 38],
|
|
||||||
popupAnchor: [0, -40]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const spbuGreenIcon = makeSpbuIcon(true);
|
|
||||||
const spbuRedIcon = makeSpbuIcon(false);
|
|
||||||
|
|
||||||
let isDrawingMode = false; // Akan diset true saat draw mode aktif (jalan/parsil)
|
|
||||||
|
|
||||||
|
|
||||||
// Fetch SPBU
|
|
||||||
function loadSpbu() {
|
|
||||||
spbuLayer.clearLayers();
|
|
||||||
fetch('api/spbu/read.php')
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'success' && data.data) {
|
|
||||||
data.data.forEach(item => {
|
|
||||||
addSpbuMarker(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (window.refreshActivePanel) window.refreshActivePanel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSpbuMarker(item) {
|
|
||||||
const icon = makeSpbuIcon(item.is_24_jam);
|
|
||||||
const marker = L.marker([item.lat, item.lng], {
|
|
||||||
icon: icon,
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
marker.spbuData = item; // Simpan data di objek marker
|
|
||||||
|
|
||||||
// Hitung popupContent sekali saat marker dibuat
|
|
||||||
const d = item;
|
|
||||||
const popupContent = `
|
|
||||||
<div style="font-family: Arial, sans-serif; min-width: 150px;">
|
|
||||||
<h4 style="margin:0 0 5px 0;">SPBU ${d.nama}</h4>
|
|
||||||
<p style="margin: 0 0 5px 0;"><b>No. WA:</b> ${d.no_wa}</p>
|
|
||||||
<p style="margin: 0 0 10px 0;"><b>Buka 24 Jam:</b> ${d.is_24_jam ? 'Ya' : 'Tidak'}</p>
|
|
||||||
<div style="display:flex; gap:5px;">
|
|
||||||
<button style="padding:4px 8px; background:#007bff; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="openEditSpbuModal(${d.id})">Edit</button>
|
|
||||||
<button style="padding:4px 8px; background:#dc3545; color:white; border:none; border-radius:3px; cursor:pointer;" onclick="deleteSpbu(${d.id})">Hapus</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
marker.bindPopup(popupContent);
|
|
||||||
|
|
||||||
// Event drag untuk update koordinat
|
|
||||||
marker.on('dragend', function(e) {
|
|
||||||
const newPos = marker.getLatLng();
|
|
||||||
updateSpbu(item.id, item.nama, item.no_wa, item.is_24_jam, newPos.lat, newPos.lng);
|
|
||||||
});
|
|
||||||
|
|
||||||
spbuLayer.addLayer(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map Click -> Form Add
|
|
||||||
map.on('click', function(e) {
|
|
||||||
if (isDrawingMode) return; // Jangan muncul form jika sedang gambar garis/polygon
|
|
||||||
|
|
||||||
// Hanya proses jika mode tambah SPBU aktif
|
|
||||||
if (window.currentAddMode === 'spbu') {
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Lat: ${e.latlng.lat.toFixed(6)}, Lng: ${e.latlng.lng.toFixed(6)}</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama SPBU</label>
|
|
||||||
<input type="text" id="modalSpbuNama" placeholder="Nama SPBU">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>No. WA</label>
|
|
||||||
<input type="text" id="modalSpbuWa" placeholder="No. WA">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Buka 24 Jam?</label>
|
|
||||||
<div class="radio-group" style="margin-top:5px;">
|
|
||||||
<label><input type="radio" name="modalSpbu24" value="1"> Ya</label>
|
|
||||||
<label><input type="radio" name="modalSpbu24" value="0" checked> Tidak</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
openModal("Tambah SPBU Baru", bodyHTML, function() {
|
|
||||||
window.saveNewSpbu(e.latlng.lat, e.latlng.lng, 'modalSpbuNama', 'modalSpbuWa', 'modalSpbu24');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Nonaktifkan mode tambah setelah modal muncul
|
|
||||||
window.deactivateAddMode();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.saveNewSpbu = function(lat, lng, namaId, waId, radioName) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
if (!nama) {
|
|
||||||
alert("Nama SPBU harus diisi!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const no_wa = document.getElementById(waId).value;
|
|
||||||
const is_24_jam = document.querySelector(`input[name="${radioName}"]:checked`).value;
|
|
||||||
|
|
||||||
fetch('api/spbu/create.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ nama, no_wa, is_24_jam, lat, lng })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
closeModal();
|
|
||||||
loadSpbu();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.openEditSpbuModal = function(id) {
|
|
||||||
let d = null;
|
|
||||||
spbuLayer.eachLayer(function(layer) {
|
|
||||||
if (layer.spbuData && layer.spbuData.id == id) d = layer.spbuData;
|
|
||||||
});
|
|
||||||
if (!d) return;
|
|
||||||
|
|
||||||
const bodyHTML = `
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama SPBU</label>
|
|
||||||
<input type="text" id="editSpbuNama" value="${d.nama}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>No. WA</label>
|
|
||||||
<input type="text" id="editSpbuWa" value="${d.no_wa}">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Buka 24 Jam?</label>
|
|
||||||
<div class="radio-group" style="margin-top:5px;">
|
|
||||||
<label><input type="radio" name="editSpbu24" value="1" ${d.is_24_jam ? 'checked' : ''}> Ya</label>
|
|
||||||
<label><input type="radio" name="editSpbu24" value="0" ${!d.is_24_jam ? 'checked' : ''}> Tidak</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
map.closePopup();
|
|
||||||
openModal("Edit SPBU", bodyHTML, function() {
|
|
||||||
window.saveEditSpbu(d.id, d.lat, d.lng, 'editSpbuNama', 'editSpbuWa', 'editSpbu24');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.saveEditSpbu = function(id, lat, lng, namaId, waId, radioName) {
|
|
||||||
const nama = document.getElementById(namaId).value;
|
|
||||||
const no_wa = document.getElementById(waId).value;
|
|
||||||
const is_24_jam = document.querySelector(`input[name="${radioName}"]:checked`).value;
|
|
||||||
|
|
||||||
updateSpbu(id, nama, no_wa, is_24_jam, lat, lng);
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateSpbu(id, nama, no_wa, is_24_jam, lat, lng) {
|
|
||||||
fetch('api/spbu/update.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id, nama, no_wa, is_24_jam, lat, lng })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
closeModal();
|
|
||||||
loadSpbu();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.deleteSpbu = function(id) {
|
|
||||||
openConfirmModal("Yakin hapus SPBU ini?", function() {
|
|
||||||
fetch('api/spbu/delete.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
if(data.status === 'success') {
|
|
||||||
map.closePopup();
|
|
||||||
loadSpbu();
|
|
||||||
} else {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial Load
|
|
||||||
loadSpbu();
|
|
||||||
@@ -29,9 +29,6 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
// Inisialisasi FeatureGroups untuk masing-masing layer
|
// Inisialisasi FeatureGroups untuk masing-masing layer
|
||||||
const spbuLayer = L.featureGroup().addTo(map);
|
|
||||||
const jalanLayer = L.featureGroup().addTo(map);
|
|
||||||
const parsilLayer = L.featureGroup().addTo(map);
|
|
||||||
const rumahIbadahLayer = L.featureGroup().addTo(map);
|
const rumahIbadahLayer = L.featureGroup().addTo(map);
|
||||||
const pendudukMiskinLayer = L.featureGroup().addTo(map);
|
const pendudukMiskinLayer = L.featureGroup().addTo(map);
|
||||||
const geoJsonLayer = L.featureGroup().addTo(map);
|
const geoJsonLayer = L.featureGroup().addTo(map);
|
||||||
@@ -66,29 +63,7 @@ window.showSearchResults = function(query) {
|
|||||||
resultsContainer.innerHTML = '';
|
resultsContainer.innerHTML = '';
|
||||||
let results = [];
|
let results = [];
|
||||||
|
|
||||||
if (typeof spbuLayer !== 'undefined') {
|
|
||||||
spbuLayer.eachLayer(layer => {
|
|
||||||
if (layer.spbuData && layer.spbuData.nama && layer.spbuData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'SPBU', nama: layer.spbuData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof jalanLayer !== 'undefined') {
|
|
||||||
jalanLayer.eachLayer(layer => {
|
|
||||||
if (layer.jalanData && layer.jalanData.nama && layer.jalanData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Jalan', nama: layer.jalanData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof parsilLayer !== 'undefined') {
|
|
||||||
parsilLayer.eachLayer(layer => {
|
|
||||||
if (layer.parsilData && layer.parsilData.nama && layer.parsilData.nama.toLowerCase().includes(query)) {
|
|
||||||
results.push({ type: 'Parsil', nama: layer.parsilData.nama, layer: layer });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof rumahIbadahLayer !== 'undefined') {
|
if (typeof rumahIbadahLayer !== 'undefined') {
|
||||||
rumahIbadahLayer.eachLayer(layer => {
|
rumahIbadahLayer.eachLayer(layer => {
|
||||||
@@ -143,15 +118,6 @@ map.on('click', function() {
|
|||||||
|
|
||||||
|
|
||||||
// Logic untuk Toggle Layer Visibility
|
// Logic untuk Toggle Layer Visibility
|
||||||
document.getElementById('layerSpbu').addEventListener('change', function(e) {
|
|
||||||
e.target.checked ? map.addLayer(spbuLayer) : map.removeLayer(spbuLayer);
|
|
||||||
});
|
|
||||||
document.getElementById('layerJalan').addEventListener('change', function(e) {
|
|
||||||
e.target.checked ? map.addLayer(jalanLayer) : map.removeLayer(jalanLayer);
|
|
||||||
});
|
|
||||||
document.getElementById('layerParsil').addEventListener('change', function(e) {
|
|
||||||
e.target.checked ? map.addLayer(parsilLayer) : map.removeLayer(parsilLayer);
|
|
||||||
});
|
|
||||||
document.getElementById('layerRumahIbadah').addEventListener('change', function(e) {
|
document.getElementById('layerRumahIbadah').addEventListener('change', function(e) {
|
||||||
e.target.checked ? map.addLayer(rumahIbadahLayer) : map.removeLayer(rumahIbadahLayer);
|
e.target.checked ? map.addLayer(rumahIbadahLayer) : map.removeLayer(rumahIbadahLayer);
|
||||||
});
|
});
|
||||||
@@ -170,44 +136,6 @@ window.toggleSubLayer = function(subId, iconEl) {
|
|||||||
|
|
||||||
// --- Sub-layer Filter ---
|
// --- Sub-layer Filter ---
|
||||||
window.applySubFilter = function(type) {
|
window.applySubFilter = function(type) {
|
||||||
if (type === 'spbu') {
|
|
||||||
const checked = [...document.querySelectorAll('.sub-spbu:checked')].map(el => el.value);
|
|
||||||
spbuLayer.eachLayer(layer => {
|
|
||||||
if (!layer.spbuData) return;
|
|
||||||
const val = layer.spbuData.is_24_jam ? '1' : '0';
|
|
||||||
if (checked.includes(val)) {
|
|
||||||
if (!spbuLayer.hasLayer(layer)) spbuLayer.addLayer(layer);
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = '');
|
|
||||||
} else {
|
|
||||||
layer.getElement && layer.getElement() && (layer.getElement().style.display = 'none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'jalan') {
|
|
||||||
const checked = [...document.querySelectorAll('.sub-jalan:checked')].map(el => el.value);
|
|
||||||
jalanLayer.eachLayer(layer => {
|
|
||||||
if (!layer.jalanData) return;
|
|
||||||
const visible = checked.includes(layer.jalanData.status);
|
|
||||||
const display = visible ? '' : 'none';
|
|
||||||
// Sembunyikan garis polyline
|
|
||||||
if (layer._path) layer._path.style.display = display;
|
|
||||||
// Sembunyikan label teks (leaflet-textpath simpan di _textNode)
|
|
||||||
if (layer._textNode) layer._textNode.style.display = display;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'parsil') {
|
|
||||||
const checked = [...document.querySelectorAll('.sub-parsil:checked')].map(el => el.value);
|
|
||||||
parsilLayer.eachLayer(layer => {
|
|
||||||
if (!layer.parsilData) return;
|
|
||||||
if (checked.includes(layer.parsilData.status)) {
|
|
||||||
layer._path && (layer._path.style.display = '');
|
|
||||||
} else {
|
|
||||||
layer._path && (layer._path.style.display = 'none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'miskin') {
|
if (type === 'miskin') {
|
||||||
const checked = [...document.querySelectorAll('.sub-miskin:checked')].map(el => el.value);
|
const checked = [...document.querySelectorAll('.sub-miskin:checked')].map(el => el.value);
|
||||||
@@ -269,8 +197,7 @@ window.closeConfirmModal = function() {
|
|||||||
|
|
||||||
// --- Action Menu Logic (replaced by left sidebar) ---
|
// --- Action Menu Logic (replaced by left sidebar) ---
|
||||||
|
|
||||||
window.currentAddMode = null; // 'spbu' atau 'rumah_ibadah'
|
window.currentAddMode = null; // 'rumah_ibadah' atau 'miskin_click'
|
||||||
window.currentDrawMode = null; // 'polyline' atau 'polygon'
|
|
||||||
|
|
||||||
window.activateAddMode = function(mode) {
|
window.activateAddMode = function(mode) {
|
||||||
// Toggle: jika mode yang sama diklik lagi, batalkan
|
// Toggle: jika mode yang sama diklik lagi, batalkan
|
||||||
@@ -279,18 +206,10 @@ window.activateAddMode = function(mode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset draw mode if active
|
|
||||||
if (window.activeDrawHandler) {
|
|
||||||
window.activeDrawHandler.disable();
|
|
||||||
window.activeDrawHandler = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.currentAddMode = mode;
|
window.currentAddMode = mode;
|
||||||
window.currentDrawMode = null;
|
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
const tooltips = {
|
const tooltips = {
|
||||||
'spbu': 'Klik untuk Tambah SPBU',
|
|
||||||
'rumah_ibadah': 'Klik untuk Tambah Rumah Ibadah'
|
'rumah_ibadah': 'Klik untuk Tambah Rumah Ibadah'
|
||||||
};
|
};
|
||||||
if (window.cursorTooltip && tooltips[mode]) window.cursorTooltip.textContent = tooltips[mode];
|
if (window.cursorTooltip && tooltips[mode]) window.cursorTooltip.textContent = tooltips[mode];
|
||||||
@@ -301,11 +220,6 @@ window.activateAddMode = function(mode) {
|
|||||||
|
|
||||||
window.deactivateAddMode = function() {
|
window.deactivateAddMode = function() {
|
||||||
window.currentAddMode = null;
|
window.currentAddMode = null;
|
||||||
window.currentDrawMode = null;
|
|
||||||
if (window.activeDrawHandler) {
|
|
||||||
window.activeDrawHandler.disable();
|
|
||||||
window.activeDrawHandler = null;
|
|
||||||
}
|
|
||||||
document.getElementById('map').style.cursor = '';
|
document.getElementById('map').style.cursor = '';
|
||||||
if (window.cursorTooltip) window.cursorTooltip.style.display = 'none';
|
if (window.cursorTooltip) window.cursorTooltip.style.display = 'none';
|
||||||
};
|
};
|
||||||
@@ -318,7 +232,7 @@ document.body.appendChild(window.cursorTooltip);
|
|||||||
// Gunakan document-level mousemove agar selalu terpicu
|
// Gunakan document-level mousemove agar selalu terpicu
|
||||||
// bahkan saat Leaflet Draw overlay aktif menangkap event map
|
// bahkan saat Leaflet Draw overlay aktif menangkap event map
|
||||||
document.addEventListener('mousemove', function(e) {
|
document.addEventListener('mousemove', function(e) {
|
||||||
if (window.currentAddMode || window.currentDrawMode) {
|
if (window.currentAddMode) {
|
||||||
window.cursorTooltip.style.display = 'block';
|
window.cursorTooltip.style.display = 'block';
|
||||||
window.cursorTooltip.style.left = e.pageX + 15 + 'px';
|
window.cursorTooltip.style.left = e.pageX + 15 + 'px';
|
||||||
window.cursorTooltip.style.top = e.pageY + 15 + 'px';
|
window.cursorTooltip.style.top = e.pageY + 15 + 'px';
|
||||||
|
|||||||
@@ -83,203 +83,30 @@
|
|||||||
rightPanelActions.innerHTML = '';
|
rightPanelActions.innerHTML = '';
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'spbu': renderSpbuPanel(); break;
|
|
||||||
case 'jalan': renderJalanPanel(); break;
|
|
||||||
case 'parsil': renderParsilPanel(); break;
|
|
||||||
case 'ibadah': renderIbadahPanel(); break;
|
case 'ibadah': renderIbadahPanel(); break;
|
||||||
case 'miskin': renderMiskinPanel(); break;
|
case 'miskin': renderMiskinPanel(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// ===== SPBU Panel =========
|
|
||||||
// ==========================
|
|
||||||
function renderSpbuPanel() {
|
|
||||||
rightPanelTitle.textContent = '⛽ SPBU';
|
|
||||||
rightPanelActions.innerHTML = `
|
|
||||||
<button class="btn-panel-add" id="btnPanelAddSpbu">
|
|
||||||
<i class="fas fa-plus"></i> Tambah SPBU
|
|
||||||
</button>`;
|
|
||||||
document.getElementById('btnPanelAddSpbu').addEventListener('click', () => {
|
|
||||||
window.activateAddMode('spbu');
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = [];
|
|
||||||
spbuLayer.eachLayer(l => { if (l.spbuData) items.push(l.spbuData); });
|
|
||||||
|
|
||||||
if (!items.length) {
|
|
||||||
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data SPBU</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rightPanelBody.innerHTML = '';
|
|
||||||
items.forEach(d => {
|
|
||||||
const bdg = d.is_24_jam
|
|
||||||
? '<span class="data-card-badge badge-green">24 Jam</span>'
|
|
||||||
: '<span class="data-card-badge badge-red">Tidak 24 Jam</span>';
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'data-card';
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="data-card-main">
|
|
||||||
<div class="data-card-icon card-icon-spbu">⛽</div>
|
|
||||||
<div class="data-card-info">
|
|
||||||
<div class="data-card-name">${escHtml(d.nama)}</div>
|
|
||||||
<div class="data-card-sub">📞 ${d.no_wa || '-'}</div>
|
|
||||||
${bdg}
|
|
||||||
</div>
|
|
||||||
<div class="data-card-actions">
|
|
||||||
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
|
||||||
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditSpbuModal(d.id); });
|
|
||||||
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteSpbu(d.id); });
|
|
||||||
card.addEventListener('click', () => flyToSpbu(d.id));
|
|
||||||
rightPanelBody.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function flyToSpbu(id) {
|
|
||||||
spbuLayer.eachLayer(l => {
|
|
||||||
if (l.spbuData && l.spbuData.id == id) { map.setView(l.getLatLng(), 17); l.openPopup(); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// ===== Jalan Panel ========
|
|
||||||
// ==========================
|
|
||||||
function renderJalanPanel() {
|
|
||||||
rightPanelTitle.textContent = '🛣️ Jalan';
|
|
||||||
rightPanelActions.innerHTML = `
|
|
||||||
<button class="btn-panel-add" id="btnPanelAddJalan">
|
|
||||||
<i class="fas fa-plus"></i> Tambah Jalan
|
|
||||||
</button>`;
|
|
||||||
document.getElementById('btnPanelAddJalan').addEventListener('click', () => {
|
|
||||||
window.activateDraw('polyline');
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = [];
|
|
||||||
jalanLayer.eachLayer(l => { if (l.jalanData) items.push(l.jalanData); });
|
|
||||||
|
|
||||||
if (!items.length) {
|
|
||||||
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data jalan</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusBadge = {
|
|
||||||
'Nasional' : '<span class="data-card-badge badge-red">Nasional</span>',
|
|
||||||
'Provinsi' : '<span class="data-card-badge badge-blue">Provinsi</span>',
|
|
||||||
'Kabupaten': '<span class="data-card-badge badge-green">Kabupaten</span>',
|
|
||||||
};
|
|
||||||
|
|
||||||
rightPanelBody.innerHTML = '';
|
|
||||||
items.forEach(d => {
|
|
||||||
const km = (d.panjang / 1000).toFixed(2);
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'data-card';
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="data-card-main">
|
|
||||||
<div class="data-card-icon card-icon-jalan">🛣️</div>
|
|
||||||
<div class="data-card-info">
|
|
||||||
<div class="data-card-name">${escHtml(d.nama)}</div>
|
|
||||||
<div class="data-card-sub">📏 ${km} km</div>
|
|
||||||
${statusBadge[d.status] || ''}
|
|
||||||
</div>
|
|
||||||
<div class="data-card-actions">
|
|
||||||
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
|
||||||
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditJalanModal(d.id); });
|
|
||||||
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteJalan(d.id); });
|
|
||||||
card.addEventListener('click', () => flyToJalan(d.id));
|
|
||||||
rightPanelBody.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function flyToJalan(id) {
|
|
||||||
jalanLayer.eachLayer(l => {
|
|
||||||
if (l.jalanData && l.jalanData.id == id && l.getBounds) {
|
|
||||||
map.fitBounds(l.getBounds(), { padding: [40, 40] }); l.openPopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// ===== Parsil Panel =======
|
|
||||||
// ==========================
|
|
||||||
function renderParsilPanel() {
|
|
||||||
rightPanelTitle.textContent = '🗺️ Parsil Tanah';
|
|
||||||
rightPanelActions.innerHTML = `
|
|
||||||
<button class="btn-panel-add" id="btnPanelAddParsil">
|
|
||||||
<i class="fas fa-plus"></i> Tambah Parsil
|
|
||||||
</button>`;
|
|
||||||
document.getElementById('btnPanelAddParsil').addEventListener('click', () => {
|
|
||||||
window.activateDraw('polygon');
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = [];
|
|
||||||
parsilLayer.eachLayer(l => { if (l.parsilData) items.push(l.parsilData); });
|
|
||||||
|
|
||||||
if (!items.length) {
|
|
||||||
rightPanelBody.innerHTML = '<div class="panel-empty">Belum ada data parsil</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusBadge = {
|
|
||||||
'SHM': '<span class="data-card-badge badge-green">SHM</span>',
|
|
||||||
'HGB': '<span class="data-card-badge badge-blue">HGB</span>',
|
|
||||||
'HGU': '<span class="data-card-badge badge-yellow">HGU</span>',
|
|
||||||
'HP' : '<span class="data-card-badge badge-orange">HP</span>',
|
|
||||||
};
|
|
||||||
|
|
||||||
rightPanelBody.innerHTML = '';
|
|
||||||
items.forEach(d => {
|
|
||||||
const luas = d.luas >= 10000
|
|
||||||
? (d.luas / 10000).toFixed(2) + ' ha'
|
|
||||||
: d.luas.toFixed(0) + ' m²';
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'data-card';
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="data-card-main">
|
|
||||||
<div class="data-card-icon card-icon-parsil">🗺️</div>
|
|
||||||
<div class="data-card-info">
|
|
||||||
<div class="data-card-name">${escHtml(d.nama || 'Tanpa Nama')}</div>
|
|
||||||
<div class="data-card-sub">📐 ${luas}</div>
|
|
||||||
${statusBadge[d.status] || ''}
|
|
||||||
</div>
|
|
||||||
<div class="data-card-actions">
|
|
||||||
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
|
||||||
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditParsilModal(d.id); });
|
|
||||||
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteParsil(d.id); });
|
|
||||||
card.addEventListener('click', () => flyToParsil(d.id));
|
|
||||||
rightPanelBody.appendChild(card);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function flyToParsil(id) {
|
|
||||||
parsilLayer.eachLayer(l => {
|
|
||||||
if (l.parsilData && l.parsilData.id == id && l.getBounds) {
|
|
||||||
map.fitBounds(l.getBounds(), { padding: [40, 40] }); l.openPopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// ===== Ibadah Panel =======
|
// ===== Ibadah Panel =======
|
||||||
// ==========================
|
// ==========================
|
||||||
function renderIbadahPanel() {
|
function renderIbadahPanel() {
|
||||||
rightPanelTitle.textContent = '🕌 Rumah Ibadah';
|
rightPanelTitle.textContent = '🕌 Rumah Ibadah';
|
||||||
rightPanelActions.innerHTML = `
|
const isAdmin = !!(window.currentUser && window.currentUser.role === 'admin');
|
||||||
<button class="btn-panel-add" id="btnPanelAddIbadah">
|
if (isAdmin) {
|
||||||
<i class="fas fa-plus"></i> Tambah Ibadah
|
rightPanelActions.innerHTML = `
|
||||||
</button>`;
|
<button class="btn-panel-add" id="btnPanelAddIbadah">
|
||||||
document.getElementById('btnPanelAddIbadah').addEventListener('click', () => {
|
<i class="fas fa-plus"></i> Tambah Ibadah
|
||||||
window.activateAddMode('rumah_ibadah');
|
</button>`;
|
||||||
});
|
document.getElementById('btnPanelAddIbadah').addEventListener('click', () => {
|
||||||
|
window.activateAddMode('rumah_ibadah');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
rumahIbadahLayer.eachLayer(l => {
|
rumahIbadahLayer.eachLayer(l => {
|
||||||
@@ -296,6 +123,15 @@
|
|||||||
const emoji = IBADAH_EMOJI[d.jenis] || '🕌';
|
const emoji = IBADAH_EMOJI[d.jenis] || '🕌';
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'data-card';
|
card.className = 'data-card';
|
||||||
|
let actionButtons = '';
|
||||||
|
if (isAdmin) {
|
||||||
|
actionButtons = `
|
||||||
|
<div class="data-card-actions">
|
||||||
|
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
||||||
|
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="data-card-main">
|
<div class="data-card-main">
|
||||||
<div class="data-card-icon card-icon-ibadah">${emoji}</div>
|
<div class="data-card-icon card-icon-ibadah">${emoji}</div>
|
||||||
@@ -303,13 +139,12 @@
|
|||||||
<div class="data-card-name">${escHtml(d.nama)}</div>
|
<div class="data-card-name">${escHtml(d.nama)}</div>
|
||||||
<div class="data-card-sub">${escHtml(d.jenis || 'Masjid')} • 📡 ${d.radius} m</div>
|
<div class="data-card-sub">${escHtml(d.jenis || 'Masjid')} • 📡 ${d.radius} m</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-card-actions">
|
${actionButtons}
|
||||||
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
|
||||||
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditIbadahModal(d.id); });
|
if (isAdmin) {
|
||||||
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteIbadah(d.id); });
|
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditIbadahModal(d.id); });
|
||||||
|
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteIbadah(d.id); });
|
||||||
|
}
|
||||||
card.addEventListener('click', () => flyToIbadah(d.id));
|
card.addEventListener('click', () => flyToIbadah(d.id));
|
||||||
rightPanelBody.appendChild(card);
|
rightPanelBody.appendChild(card);
|
||||||
});
|
});
|
||||||
@@ -328,31 +163,34 @@
|
|||||||
// ==========================
|
// ==========================
|
||||||
function renderMiskinPanel() {
|
function renderMiskinPanel() {
|
||||||
rightPanelTitle.textContent = '🏠 Penduduk Miskin';
|
rightPanelTitle.textContent = '🏠 Penduduk Miskin';
|
||||||
rightPanelActions.innerHTML = `
|
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
|
||||||
<button class="btn-panel-add" id="btnPanelTambahMiskin">
|
if (canManage) {
|
||||||
<i class="fas fa-plus"></i> Tambah
|
rightPanelActions.innerHTML = `
|
||||||
</button>
|
<button class="btn-panel-add" id="btnPanelTambahMiskin">
|
||||||
<button class="btn-panel-import" id="btnPanelImportMiskin">
|
<i class="fas fa-plus"></i> Tambah
|
||||||
<i class="fas fa-file-import"></i> Impor
|
</button>
|
||||||
</button>`;
|
<button class="btn-panel-import" id="btnPanelImportMiskin">
|
||||||
|
<i class="fas fa-file-import"></i> Impor
|
||||||
|
</button>`;
|
||||||
|
|
||||||
// Tombol Tambah: buka form via right-click popup di peta
|
document.getElementById('btnPanelTambahMiskin').addEventListener('click', () => {
|
||||||
document.getElementById('btnPanelTambahMiskin').addEventListener('click', () => {
|
window.currentAddMode = 'miskin_click';
|
||||||
// Aktifkan mode: klik kiri di peta = tambah miskin
|
document.getElementById('map').style.cursor = 'crosshair';
|
||||||
window.currentAddMode = 'miskin_click';
|
if (window.cursorTooltip) {
|
||||||
document.getElementById('map').style.cursor = 'crosshair';
|
window.cursorTooltip.textContent = 'Klik di peta untuk tentukan lokasi penduduk miskin';
|
||||||
if (window.cursorTooltip) {
|
}
|
||||||
window.cursorTooltip.textContent = 'Klik di peta untuk tentukan lokasi penduduk miskin';
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('btnPanelImportMiskin').addEventListener('click', () => {
|
document.getElementById('btnPanelImportMiskin').addEventListener('click', () => {
|
||||||
if (typeof window.openImportMiskinModal === 'function') {
|
if (typeof window.openImportMiskinModal === 'function') {
|
||||||
window.openImportMiskinModal();
|
window.openImportMiskinModal();
|
||||||
} else {
|
} else {
|
||||||
alert('Fitur impor belum siap.');
|
alert('Fitur impor belum siap.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
rightPanelActions.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
const all = [];
|
const all = [];
|
||||||
pendudukMiskinLayer.eachLayer(l => { if (l.miskinData) all.push(l.miskinData); });
|
pendudukMiskinLayer.eachLayer(l => { if (l.miskinData) all.push(l.miskinData); });
|
||||||
@@ -394,6 +232,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMiskinCard(d, terurus) {
|
function buildMiskinCard(d, terurus) {
|
||||||
|
const canManage = !!(window.currentUser && (window.currentUser.role === 'admin' || window.currentUser.role === 'pengelola'));
|
||||||
const iconCls = terurus ? 'card-icon-miskin-in' : 'card-icon-miskin-out';
|
const iconCls = terurus ? 'card-icon-miskin-in' : 'card-icon-miskin-out';
|
||||||
const badge = d.kategori_bantuan === 'Makan'
|
const badge = d.kategori_bantuan === 'Makan'
|
||||||
? '<span class="data-card-badge badge-orange">Bantuan Makan</span>'
|
? '<span class="data-card-badge badge-orange">Bantuan Makan</span>'
|
||||||
@@ -402,6 +241,41 @@
|
|||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'data-card';
|
card.className = 'data-card';
|
||||||
card.dataset.miskinId = d.id;
|
card.dataset.miskinId = d.id;
|
||||||
|
|
||||||
|
let actionButtons = '';
|
||||||
|
if (canManage) {
|
||||||
|
actionButtons = `
|
||||||
|
<div class="data-card-actions">
|
||||||
|
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
||||||
|
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fotoHtml = '';
|
||||||
|
if (d.foto_rumah || d.foto_kk) {
|
||||||
|
fotoHtml += `<div class="expand-row-photos" style="display:flex; gap:10px; margin-top:8px; justify-content: space-around;">`;
|
||||||
|
if (d.foto_rumah) {
|
||||||
|
fotoHtml += `
|
||||||
|
<div style="text-align:center;">
|
||||||
|
<span style="font-size:10px; display:block; font-weight:500; color:#555;">Foto Rumah</span>
|
||||||
|
<a href="uploads/${d.foto_rumah}" target="_blank">
|
||||||
|
<img src="uploads/${d.foto_rumah}" style="width:80px; height:60px; object-fit:cover; border-radius:4px; border:1px solid #ddd; margin-top:3px;" />
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (d.foto_kk) {
|
||||||
|
fotoHtml += `
|
||||||
|
<div style="text-align:center;">
|
||||||
|
<span style="font-size:10px; display:block; font-weight:500; color:#555;">Foto KK</span>
|
||||||
|
<a href="uploads/${d.foto_kk}" target="_blank">
|
||||||
|
<img src="uploads/${d.foto_kk}" style="width:80px; height:60px; object-fit:cover; border-radius:4px; border:1px solid #ddd; margin-top:3px;" />
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
fotoHtml += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="data-card-main">
|
<div class="data-card-main">
|
||||||
<div class="data-card-icon ${iconCls}">🏠</div>
|
<div class="data-card-icon ${iconCls}">🏠</div>
|
||||||
@@ -410,22 +284,22 @@
|
|||||||
<div class="data-card-sub">👨👩👧 ${d.jumlah_jiwa || '-'} jiwa</div>
|
<div class="data-card-sub">👨👩👧 ${d.jumlah_jiwa || '-'} jiwa</div>
|
||||||
${badge}
|
${badge}
|
||||||
</div>
|
</div>
|
||||||
<div class="data-card-actions">
|
${actionButtons}
|
||||||
<button class="btn-card-action btn-card-edit" title="Edit"><i class="fas fa-pen"></i></button>
|
|
||||||
<button class="btn-card-action btn-card-delete" title="Hapus"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="data-card-expand">
|
<div class="data-card-expand">
|
||||||
<div class="expand-row"><span>Kategori Bantuan</span><span>${escHtml(d.kategori_bantuan)}</span></div>
|
<div class="expand-row"><span>Kategori Bantuan</span><span>${escHtml(d.kategori_bantuan)}</span></div>
|
||||||
<div class="expand-row"><span>Jumlah Jiwa</span><span>${d.jumlah_jiwa || '-'}</span></div>
|
<div class="expand-row"><span>Jumlah Jiwa</span><span>${d.jumlah_jiwa || '-'}</span></div>
|
||||||
<div class="expand-row"><span>Status</span><span>${terurus ? 'Terurus ✅' : 'Tidak Terurus ⚠️'}</span></div>
|
<div class="expand-row"><span>Status</span><span>${terurus ? 'Terurus ✅' : 'Tidak Terurus ⚠️'}</span></div>
|
||||||
|
${fotoHtml}
|
||||||
<button class="btn-log-bantuan" data-id="${d.id}" data-nama="${escHtml(d.nama)}">
|
<button class="btn-log-bantuan" data-id="${d.id}" data-nama="${escHtml(d.nama)}">
|
||||||
<i class="fas fa-history"></i> Lihat Log Bantuan
|
<i class="fas fa-history"></i> Lihat Log Bantuan
|
||||||
</button>
|
</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditMiskinModal(d.id); });
|
if (canManage) {
|
||||||
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteMiskin(d.id); });
|
card.querySelector('.btn-card-edit').addEventListener('click', (e) => { e.stopPropagation(); openEditMiskinModal(d.id); });
|
||||||
|
card.querySelector('.btn-card-delete').addEventListener('click', (e) => { e.stopPropagation(); deleteMiskin(d.id); });
|
||||||
|
}
|
||||||
card.querySelector('.btn-log-bantuan').addEventListener('click', (e) => { e.stopPropagation(); openLogBantuan(d.id, d.nama); });
|
card.querySelector('.btn-log-bantuan').addEventListener('click', (e) => { e.stopPropagation(); openLogBantuan(d.id, d.nama); });
|
||||||
|
|
||||||
card.querySelector('.data-card-main').addEventListener('click', () => {
|
card.querySelector('.data-card-main').addEventListener('click', () => {
|
||||||
@@ -473,6 +347,18 @@
|
|||||||
logTitle.textContent = `Log Bantuan — ${nama}`;
|
logTitle.textContent = `Log Bantuan — ${nama}`;
|
||||||
logModal.classList.add('show');
|
logModal.classList.add('show');
|
||||||
|
|
||||||
|
const isAuth = !!window.currentUser;
|
||||||
|
const formContainer = document.querySelector('#logBantuanModal .modal-body > div:last-child');
|
||||||
|
const modalFooter = document.querySelector('#logBantuanModal .modal-footer');
|
||||||
|
|
||||||
|
if (isAuth) {
|
||||||
|
if (formContainer) formContainer.style.display = 'block';
|
||||||
|
if (modalFooter) modalFooter.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
if (formContainer) formContainer.style.display = 'none';
|
||||||
|
if (modalFooter) modalFooter.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// Isi dropdown ibadah
|
// Isi dropdown ibadah
|
||||||
logIbadahSel.innerHTML = '<option value="">-- Pilih Rumah Ibadah --</option>';
|
logIbadahSel.innerHTML = '<option value="">-- Pilih Rumah Ibadah --</option>';
|
||||||
if (typeof ibadahDataList !== 'undefined') {
|
if (typeof ibadahDataList !== 'undefined') {
|
||||||
@@ -484,6 +370,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.currentUser && window.currentUser.role === 'pengelola') {
|
||||||
|
logIbadahSel.value = window.currentUser.ibadah_id;
|
||||||
|
logIbadahSel.disabled = true;
|
||||||
|
} else {
|
||||||
|
logIbadahSel.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set tanggal default hari ini
|
// Set tanggal default hari ini
|
||||||
document.getElementById('logTanggal').valueAsDate = new Date();
|
document.getElementById('logTanggal').valueAsDate = new Date();
|
||||||
fetchLogBantuan(miskinId);
|
fetchLogBantuan(miskinId);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ CREATE TABLE `penduduk_miskin` (
|
|||||||
`jumlah_jiwa` int(11) NOT NULL DEFAULT 1,
|
`jumlah_jiwa` int(11) NOT NULL DEFAULT 1,
|
||||||
`lat` double NOT NULL,
|
`lat` double NOT NULL,
|
||||||
`lng` double NOT NULL,
|
`lng` double NOT NULL,
|
||||||
|
`foto_rumah` varchar(255) DEFAULT NULL,
|
||||||
|
`foto_kk` varchar(255) DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
@@ -44,39 +46,6 @@ CREATE TABLE `log_bantuan` (
|
|||||||
CONSTRAINT `fk_log_miskin` FOREIGN KEY (`miskin_id`) REFERENCES `penduduk_miskin` (`id`) ON DELETE CASCADE
|
CONSTRAINT `fk_log_miskin` FOREIGN KEY (`miskin_id`) REFERENCES `penduduk_miskin` (`id`) ON DELETE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
-- 4. Tabel jalan
|
|
||||||
DROP TABLE IF EXISTS `jalan`;
|
|
||||||
CREATE TABLE `jalan` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`nama` varchar(255) NOT NULL,
|
|
||||||
`status` varchar(100) NOT NULL DEFAULT 'Kabupaten',
|
|
||||||
`panjang` double NOT NULL DEFAULT 0,
|
|
||||||
`geom` geometry NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- 5. Tabel parsil
|
|
||||||
DROP TABLE IF EXISTS `parsil`;
|
|
||||||
CREATE TABLE `parsil` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`nama` varchar(255) NOT NULL,
|
|
||||||
`status` varchar(100) NOT NULL DEFAULT 'SHM',
|
|
||||||
`luas` double NOT NULL DEFAULT 0,
|
|
||||||
`geom` geometry NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- 6. Tabel spbu
|
|
||||||
DROP TABLE IF EXISTS `spbu`;
|
|
||||||
CREATE TABLE `spbu` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`nama` varchar(255) NOT NULL,
|
|
||||||
`no_wa` varchar(20) DEFAULT NULL,
|
|
||||||
`is_24_jam` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`lat` double NOT NULL,
|
|
||||||
`lng` double NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- Data Contoh (Sample Data) untuk Pontianak
|
-- Data Contoh (Sample Data) untuk Pontianak
|
||||||
|
|
||||||
@@ -98,20 +67,24 @@ INSERT INTO `log_bantuan` (`miskin_id`, `ibadah_id`, `tipe_bantuan`, `tanggal`,
|
|||||||
(2, 2, 'Pendidikan', '2026-05-20', 'Bantuan biaya sekolah anak'),
|
(2, 2, 'Pendidikan', '2026-05-20', 'Bantuan biaya sekolah anak'),
|
||||||
(3, 1, 'Uang Tunai', '2026-06-01', 'Santunan tunai darurat');
|
(3, 1, 'Uang Tunai', '2026-06-01', 'Santunan tunai darurat');
|
||||||
|
|
||||||
-- Data Contoh SPBU
|
|
||||||
INSERT INTO `spbu` (`nama`, `no_wa`, `is_24_jam`, `lat`, `lng`) VALUES
|
|
||||||
('SPBU Ahmad Yani', '081234567890', 1, -0.0485, 109.3490),
|
|
||||||
('SPBU Oesman Sapta Odang', '081234567891', 0, -0.0345, 109.3512),
|
|
||||||
('SPBU Kota Baru', '081234567892', 1, -0.0512, 109.3198);
|
|
||||||
|
|
||||||
-- Data Contoh Jalan (Polyline)
|
|
||||||
-- Menggunakan ST_GeomFromText untuk membuat LineString
|
|
||||||
INSERT INTO `jalan` (`nama`, `status`, `panjang`, `geom`) VALUES
|
|
||||||
('Jl. Jenderal Ahmad Yani', 'Nasional', 2500, ST_GeomFromText('LINESTRING(109.3400 -0.0380, 109.3450 -0.0450, 109.3500 -0.0520)')),
|
|
||||||
('Jl. Gajah Mada', 'Provinsi', 1800, ST_GeomFromText('LINESTRING(109.3350 -0.0290, 109.3410 -0.0280, 109.3480 -0.0270)'));
|
|
||||||
|
|
||||||
-- Data Contoh Parsil (Polygon)
|
-- 7. Tabel users
|
||||||
-- Menggunakan ST_GeomFromText untuk membuat Polygon (pastikan koordinat berputar kembali ke titik awal untuk menutup polygon)
|
DROP TABLE IF EXISTS `users`;
|
||||||
INSERT INTO `parsil` (`nama`, `status`, `luas`, `geom`) VALUES
|
CREATE TABLE `users` (
|
||||||
('Parsil Tanah A', 'SHM', 150, ST_GeomFromText('POLYGON((109.3360 -0.0250, 109.3375 -0.0250, 109.3375 -0.0260, 109.3360 -0.0260, 109.3360 -0.0250))')),
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
('Parsil Tanah B', 'HGB', 320, ST_GeomFromText('POLYGON((109.3405 -0.0310, 109.3420 -0.0310, 109.3420 -0.0330, 109.3405 -0.0330, 109.3405 -0.0310))'));
|
`username` varchar(100) NOT NULL UNIQUE,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`role` enum('admin', 'pengelola') NOT NULL,
|
||||||
|
`ibadah_id` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
CONSTRAINT `fk_users_ibadah` FOREIGN KEY (`ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- Data Contoh Pengguna (Default Users)
|
||||||
|
-- Password admin: password
|
||||||
|
-- Password pengelola: password (terhubung ke Masjid Raya Mujahidin, ibadah_id = 1)
|
||||||
|
INSERT INTO `users` (`username`, `password`, `role`, `ibadah_id`) VALUES
|
||||||
|
('admin', '$2y$10$qqKxivn4646czFu/ffwa/uZxJrkj3T9xmsrRbyxJbHx6jllpOOfDO', 'admin', NULL),
|
||||||
|
('pengelola_mujahidin', '$2y$10$qqKxivn4646czFu/ffwa/uZxJrkj3T9xmsrRbyxJbHx6jllpOOfDO', 'pengelola', 1);
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 9.5 MiB |
|
After Width: | Height: | Size: 9.3 MiB |
@@ -15,8 +15,7 @@
|
|||||||
<!-- Leaflet CSS -->
|
<!-- Leaflet CSS -->
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
|
||||||
<!-- Leaflet Draw CSS -->
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
|
||||||
|
|
||||||
<!-- Custom CSS -->
|
<!-- Custom CSS -->
|
||||||
<link rel="stylesheet" href="assets/css/style.css">
|
<link rel="stylesheet" href="assets/css/style.css">
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
<div class="search-icon">
|
<div class="search-icon">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="search-input" id="searchInput" placeholder="Cari SPBU, jalan, parsil, rumah ibadah...">
|
<input type="text" class="search-input" id="searchInput" placeholder="Cari rumah ibadah...">
|
||||||
<button class="search-clear" id="searchClear">
|
<button class="search-clear" id="searchClear">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -53,18 +52,7 @@
|
|||||||
|
|
||||||
<!-- Left Sidebar Menu -->
|
<!-- Left Sidebar Menu -->
|
||||||
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
<nav class="left-sidebar" id="leftSidebar" style="display:none;">
|
||||||
<button class="sidebar-btn" id="menuSpbu" data-panel="spbu" title="SPBU">
|
|
||||||
<span class="sidebar-emoji">⛽</span>
|
|
||||||
<span class="sidebar-label">SPBU</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuJalan" data-panel="jalan" title="Jalan">
|
|
||||||
<span class="sidebar-emoji">🛣️</span>
|
|
||||||
<span class="sidebar-label">Jalan</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuParsil" data-panel="parsil" title="Parsil Tanah">
|
|
||||||
<span class="sidebar-emoji">🗺️</span>
|
|
||||||
<span class="sidebar-label">Parsil</span>
|
|
||||||
</button>
|
|
||||||
<button class="sidebar-btn" id="menuIbadah" data-panel="ibadah" title="Rumah Ibadah">
|
<button class="sidebar-btn" id="menuIbadah" data-panel="ibadah" title="Rumah Ibadah">
|
||||||
<span class="sidebar-emoji">🕌</span>
|
<span class="sidebar-emoji">🕌</span>
|
||||||
<span class="sidebar-label">Ibadah</span>
|
<span class="sidebar-label">Ibadah</span>
|
||||||
@@ -73,6 +61,12 @@
|
|||||||
<span class="sidebar-emoji">🏠</span>
|
<span class="sidebar-emoji">🏠</span>
|
||||||
<span class="sidebar-label">Miskin</span>
|
<span class="sidebar-label">Miskin</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="sidebar-divider" id="sidebarAdminDivider" style="display:none;"></div>
|
||||||
|
<button class="sidebar-btn" id="menuUsers" style="display:none;" title="Manajemen Pengguna">
|
||||||
|
<span class="sidebar-emoji">👥</span>
|
||||||
|
<span class="sidebar-label">User</span>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
@@ -87,68 +81,9 @@
|
|||||||
<div class="custom-layer-panel" id="layerPanel">
|
<div class="custom-layer-panel" id="layerPanel">
|
||||||
<h3>Daftar Layer</h3>
|
<h3>Daftar Layer</h3>
|
||||||
|
|
||||||
<!-- SPBU -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerSpbu" checked> SPBU
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subSpbu', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subSpbu" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="1" checked onchange="applySubFilter('spbu')"> 24 Jam
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-spbu" value="0" checked onchange="applySubFilter('spbu')"> Tidak 24 Jam
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Jalan -->
|
<div class="layer-section-title">Pemetaan Kemiskinan</div>
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerJalan" checked> Jalan
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subJalan', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subJalan" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Nasional" checked onchange="applySubFilter('jalan')"> Nasional
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Provinsi" checked onchange="applySubFilter('jalan')"> Provinsi
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-jalan" value="Kabupaten" checked onchange="applySubFilter('jalan')"> Kabupaten
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Parsil -->
|
|
||||||
<div class="layer-group-item">
|
|
||||||
<div class="layer-group-header">
|
|
||||||
<label class="layer-option" style="margin:0; flex:1;">
|
|
||||||
<input type="checkbox" id="layerParsil" checked> Parsil Tanah
|
|
||||||
</label>
|
|
||||||
<span class="layer-toggle-icon collapsed" onclick="toggleSubLayer('subParsil', this)"><i class="fas fa-chevron-down"></i></span>
|
|
||||||
</div>
|
|
||||||
<div id="subParsil" class="sub-layer-list" style="display:none;">
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="SHM" checked onchange="applySubFilter('parsil')"> SHM
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGB" checked onchange="applySubFilter('parsil')"> HGB
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HGU" checked onchange="applySubFilter('parsil')"> HGU
|
|
||||||
</label>
|
|
||||||
<label class="layer-option sub-option">
|
|
||||||
<input type="checkbox" class="sub-parsil" value="HP" checked onchange="applySubFilter('parsil')"> HP
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rumah Ibadah -->
|
<!-- Rumah Ibadah -->
|
||||||
<label class="layer-option">
|
<label class="layer-option">
|
||||||
@@ -290,29 +225,166 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Floating Profile / Auth Control -->
|
||||||
|
<div id="authWidget" class="auth-widget">
|
||||||
|
<button class="btn-login" id="authLoginBtn"><i class="fas fa-sign-in-alt"></i> Login</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Modal -->
|
||||||
|
<div id="loginModal" class="unified-modal">
|
||||||
|
<div class="modal-content" style="width: 360px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Login Ke Sistem</h3>
|
||||||
|
<span class="modal-close" id="closeLoginModal">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginUsername">Username</label>
|
||||||
|
<input type="text" id="loginUsername" placeholder="Masukkan username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginPassword">Password</label>
|
||||||
|
<input type="password" id="loginPassword" placeholder="Masukkan password">
|
||||||
|
</div>
|
||||||
|
<div id="loginErrorMsg" style="display:none; color:#ef4444; font-size:12px; font-weight:500;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="loginSubmitBtn" class="btn-save" style="width: 100%;">Masuk</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Management Modal -->
|
||||||
|
<div id="userManagementModal" class="unified-modal">
|
||||||
|
<div class="modal-content" style="width: 700px; max-width: 95%;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Manajemen Pengguna (Admin)</h3>
|
||||||
|
<span class="modal-close" id="closeUserManagementModal">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" style="gap: 15px;">
|
||||||
|
<!-- Form Tambah/Edit User -->
|
||||||
|
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; gap: 10px;">
|
||||||
|
<h4 id="userFormTitle" style="font-size: 13px; font-weight:700; color:#334155; margin-bottom: 2px;">Tambah User Baru</h4>
|
||||||
|
<input type="hidden" id="manageUserId" value="">
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" id="manageUsername" placeholder="Username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Password (Kosongkan jika tidak diubah)</label>
|
||||||
|
<input type="password" id="managePassword" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Role</label>
|
||||||
|
<select id="manageRole">
|
||||||
|
<option value="pengelola" selected>Pengelola Rumah Ibadah</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="manageIbadahGroup">
|
||||||
|
<label>Rumah Ibadah yang Dikelola</label>
|
||||||
|
<select id="manageIbadahId">
|
||||||
|
<!-- Dikonstruksi dinamis dari data rumah ibadah -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 5px;">
|
||||||
|
<button id="btnCancelUserEdit" class="btn-cancel" style="padding: 6px 12px; display:none;">Batal</button>
|
||||||
|
<button id="btnSaveUser" class="btn-save" style="padding: 6px 16px;">Simpan User</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabel Daftar User -->
|
||||||
|
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #cbd5e1; border-radius: 8px;">
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 13px; text-align: left;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background: #f1f5f9; border-bottom: 1px solid #cbd5e1; color:#475569; position: sticky; top: 0; z-index: 10;">
|
||||||
|
<th style="padding: 10px 12px;">Username</th>
|
||||||
|
<th style="padding: 10px 12px;">Role</th>
|
||||||
|
<th style="padding: 10px 12px;">Rumah Ibadah</th>
|
||||||
|
<th style="padding: 10px 12px; text-align: center;">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="userTableBody">
|
||||||
|
<!-- Diisi dinamis -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Toast Notification Container -->
|
<!-- Toast Notification Container -->
|
||||||
<div id="toastContainer"></div>
|
<div id="toastContainer"></div>
|
||||||
|
|
||||||
|
<!-- Disclaimer Banner -->
|
||||||
|
<div id="disclaimerBanner" style="
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 2000;
|
||||||
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||||
|
color: #f1f5f9;
|
||||||
|
border: 1px solid #f59e0b;
|
||||||
|
border-left: 4px solid #f59e0b;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
max-width: 520px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
animation: slideUpBanner 0.4s ease;
|
||||||
|
">
|
||||||
|
<span style="font-size: 20px; flex-shrink:0; margin-top:1px;">⚠️</span>
|
||||||
|
<div style="flex:1;">
|
||||||
|
<div style="font-size: 13px; font-weight: 700; color: #fbbf24; margin-bottom: 4px; letter-spacing: 0.3px;">DISCLAIMER — DATA DUMMY</div>
|
||||||
|
<div style="font-size: 12px; line-height: 1.6; color: #cbd5e1;">
|
||||||
|
Seluruh data pada aplikasi ini termasuk <strong style="color:#fde68a;">nama penduduk, foto rumah, dan foto Kartu Keluarga (KK)</strong> merupakan <strong style="color:#fde68a;">data fiktif / dummy</strong> yang hanya digunakan untuk keperluan demonstrasi dan pengujian sistem. Bukan data nyata.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="document.getElementById('disclaimerBanner').style.display='none'" style="
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #475569;
|
||||||
|
color: #94a3b8;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
" onmouseover="this.style.borderColor='#f59e0b';this.style.color='#f59e0b'" onmouseout="this.style.borderColor='#475569';this.style.color='#94a3b8'">Tutup</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes slideUpBanner {
|
||||||
|
from { opacity: 0; transform: translateX(-50%) translateY(20px); }
|
||||||
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- Leaflet JS -->
|
<!-- Leaflet JS -->
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
<!-- Leaflet Draw JS -->
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
|
||||||
|
|
||||||
<!-- Leaflet TextPath JS (untuk teks diagonal) -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-textpath@1.2.3/leaflet.textpath.min.js"></script>
|
|
||||||
|
|
||||||
<!-- Main Map Initialization -->
|
<!-- Main Map Initialization -->
|
||||||
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
<script src="assets/js/map.js?v=<?= time() ?>"></script>
|
||||||
<!-- Fitur & Komponen -->
|
<!-- Fitur & Komponen -->
|
||||||
<script src="assets/js/features/spbu.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/jalan.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/parsil.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/draw_control.js?v=<?= time() ?>"></script>
|
|
||||||
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geolocation.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/kemiskinan.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/kemiskinan.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
<script src="assets/js/features/geojson.js?v=<?= time() ?>"></script>
|
||||||
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
<script src="assets/js/panel.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
|
<!-- Core Auth Feature -->
|
||||||
|
<script src="assets/js/features/auth.js?v=<?= time() ?>"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 9.5 MiB |
|
After Width: | Height: | Size: 9.5 MiB |
|
After Width: | Height: | Size: 9.3 MiB |
|
After Width: | Height: | Size: 9.5 MiB |
|
After Width: | Height: | Size: 9.3 MiB |