First commit

This commit is contained in:
2026-06-09 19:02:22 +07:00
commit c2c0774b3b
36 changed files with 10493 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
<?php
// ============================================================
// api_auth.php Autentikasi Pengguna dengan Role
// WebGIS Poverty Mapping
// ============================================================
require_once 'config.php';
// config.php sudah set header Content-Type & CORS.
// Jika OPTIONS preflight, sudah di-handle di config.php.
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method tidak diizinkan.']);
exit();
}
// Baca body JSON
$body = json_decode(file_get_contents('php://input'), true);
if (!$body) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Request body tidak valid.']);
exit();
}
$username = isset($body['username']) ? trim($body['username']) : '';
$password = isset($body['password']) ? $body['password'] : '';
$roleReq = isset($body['role']) ? trim($body['role']) : '';
// Validasi input
if (!$username || !$password || !$roleReq) {
echo json_encode(['success' => false, 'message' => 'Username, password, dan role wajib diisi.']);
exit();
}
$validRoles = ['admin', 'pengurus', 'walikota'];
if (!in_array($roleReq, $validRoles)) {
echo json_encode(['success' => false, 'message' => 'Role tidak dikenali.']);
exit();
}
// ── Cari user di database ──────────────────────────────────
$db = getDB();
$stmt = $db->prepare(
"SELECT id, username, nama_lengkap, role, password_hash
FROM users
WHERE username = ? AND role = ?
LIMIT 1"
);
$stmt->bind_param('ss', $username, $roleReq);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
$stmt->close();
$db->close();
echo json_encode(['success' => false, 'message' => 'Username, password, atau peran tidak sesuai.']);
exit();
}
$user = $result->fetch_assoc();
$stmt->close();
$db->close();
// ── Verifikasi password ────────────────────────────────────
if (!password_verify($password, $user['password_hash'])) {
echo json_encode(['success' => false, 'message' => 'Username, password, atau peran tidak sesuai.']);
exit();
}
// ── Login berhasil ─────────────────────────────────────────
echo json_encode([
'success' => true,
'message' => 'Login berhasil.',
'user' => [
'id' => (int)$user['id'],
'username' => $user['username'],
'nama' => $user['nama_lengkap'],
'role' => $user['role']
]
]);
?>
+129
View File
@@ -0,0 +1,129 @@
<?php
// ============================================================
// api_bantuan.php CRUD Riwayat Bantuan Kemiskinan
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
// GET: Ambil riwayat bantuan berdasarkan id_kemiskinan
if ($method === 'GET') {
$id_kemiskinan = isset($_GET['id_kemiskinan']) ? (int)$_GET['id_kemiskinan'] : 0;
if (!$id_kemiskinan) {
http_response_code(400);
echo json_encode(['error' => 'Parameter id_kemiskinan wajib diisi']);
exit();
}
$db = getDB();
$stmt = $db->prepare("SELECT * FROM riwayat_bantuan WHERE id_kemiskinan = ? ORDER BY tanggal DESC, created_at DESC");
$stmt->bind_param('i', $id_kemiskinan);
$stmt->execute();
$result = $stmt->get_result();
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
echo json_encode($data);
$db->close();
exit();
}
// POST: Tambah riwayat bantuan baru
if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
$id_kemiskinan = isset($body['id_kemiskinan']) ? (int)$body['id_kemiskinan'] : 0;
$pemberi = isset($body['pemberi']) ? trim($body['pemberi']) : '';
$jenis_bantuan = isset($body['jenis_bantuan']) ? trim($body['jenis_bantuan']) : '';
$tanggal = isset($body['tanggal']) ? trim($body['tanggal']) : '';
$keterangan = isset($body['keterangan']) ? trim($body['keterangan']) : '';
if (!$id_kemiskinan || !$pemberi || !$jenis_bantuan || !$tanggal) {
http_response_code(400);
echo json_encode(['error' => 'id_kemiskinan, pemberi, jenis_bantuan, dan tanggal wajib diisi']);
exit();
}
$db = getDB();
try {
$db->begin_transaction();
// 1. Insert ke riwayat_bantuan
$stmt = $db->prepare("INSERT INTO riwayat_bantuan (id_kemiskinan, pemberi, jenis_bantuan, tanggal, keterangan) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param('issss', $id_kemiskinan, $pemberi, $jenis_bantuan, $tanggal, $keterangan);
$stmt->execute();
$id_riwayat = $db->insert_id;
// 2. Update status_bantuan di tabel data_kemiskinan menjadi 'Sudah'
$stmtUpdate = $db->prepare("UPDATE data_kemiskinan SET status_bantuan = 'Sudah' WHERE id = ?");
$stmtUpdate->bind_param('i', $id_kemiskinan);
$stmtUpdate->execute();
$db->commit();
echo json_encode([
'success' => true,
'message' => 'Berhasil menambahkan catatan bantuan',
'id' => $id_riwayat,
'status_bantuan' => 'Sudah'
]);
} catch (Exception $e) {
$db->rollback();
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan data: ' . $e->getMessage()]);
}
$db->close();
exit();
}
// DELETE: Hapus catatan bantuan
if ($method === 'DELETE') {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$id_kemiskinan = isset($_GET['id_kemiskinan']) ? (int)$_GET['id_kemiskinan'] : 0;
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'Parameter id wajib diisi']);
exit();
}
$db = getDB();
try {
$db->begin_transaction();
$stmt = $db->prepare("DELETE FROM riwayat_bantuan WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
// Cek apakah masih ada bantuan lain
$stmtCek = $db->prepare("SELECT COUNT(*) AS total FROM riwayat_bantuan WHERE id_kemiskinan = ?");
$stmtCek->bind_param('i', $id_kemiskinan);
$stmtCek->execute();
$res = $stmtCek->get_result()->fetch_assoc();
$count = $res['total'];
$status_baru = 'Sudah';
if ($count == 0) {
$stmtUpdate = $db->prepare("UPDATE data_kemiskinan SET status_bantuan = 'Belum' WHERE id = ?");
$stmtUpdate->bind_param('i', $id_kemiskinan);
$stmtUpdate->execute();
$status_baru = 'Belum';
}
$db->commit();
echo json_encode(['success' => true, 'status_bantuan' => $status_baru]);
} catch (Exception $e) {
$db->rollback();
http_response_code(500);
echo json_encode(['error' => 'Gagal menghapus data']);
}
$db->close();
exit();
}
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
+141
View File
@@ -0,0 +1,141 @@
<?php
// ============================================================
// api_dashboard.php Data untuk Dashboard Walikota
// Endpoint statistik ringkasan, laporan terbaru, data heatmap
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
if ($method !== 'GET') {
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
exit();
}
$action = isset($_GET['action']) ? $_GET['action'] : 'stats';
$db = getDB();
// ── STATISTIK RINGKASAN ────────────────────────────────────
if ($action === 'stats') {
$stats = [];
// Total per kategori kemiskinan
$result = $db->query("SELECT kategori, COUNT(*) as total,
SUM(CASE WHEN status_bantuan = 'Sudah' THEN 1 ELSE 0 END) as sudah_dibantu,
SUM(CASE WHEN status_bantuan != 'Sudah' OR status_bantuan IS NULL THEN 1 ELSE 0 END) as belum_dibantu
FROM data_kemiskinan GROUP BY kategori");
$kategori = [];
$total_warga = 0;
$total_sudah = 0;
$total_belum = 0;
while ($row = $result->fetch_assoc()) {
$row['total'] = (int)$row['total'];
$row['sudah_dibantu'] = (int)$row['sudah_dibantu'];
$row['belum_dibantu'] = (int)$row['belum_dibantu'];
$kategori[] = $row;
$total_warga += $row['total'];
$total_sudah += $row['sudah_dibantu'];
$total_belum += $row['belum_dibantu'];
}
$stats['kategori'] = $kategori;
$stats['total_warga'] = $total_warga;
$stats['total_sudah'] = $total_sudah;
$stats['total_belum'] = $total_belum;
$stats['persen_sudah'] = $total_warga > 0 ? round(($total_sudah / $total_warga) * 100, 1) : 0;
// Total rumah ibadah
$result = $db->query("SELECT jenis, COUNT(*) as total FROM data_masjid GROUP BY jenis");
$ibadah = [];
$total_ibadah = 0;
while ($row = $result->fetch_assoc()) {
$row['total'] = (int)$row['total'];
$ibadah[] = $row;
$total_ibadah += $row['total'];
}
$stats['rumah_ibadah'] = $ibadah;
$stats['total_ibadah'] = $total_ibadah;
// Total bantuan tercatat
$result = $db->query("SELECT COUNT(*) as total FROM riwayat_bantuan");
$row = $result->fetch_assoc();
$stats['total_bantuan'] = (int)$row['total'];
// Total laporan masyarakat per status
$result = $db->query("SELECT status, COUNT(*) as total FROM laporan_masyarakat GROUP BY status");
$laporan_status = [];
while ($row = $result->fetch_assoc()) {
$row['total'] = (int)$row['total'];
$laporan_status[] = $row;
}
$stats['laporan_status'] = $laporan_status;
// Penyakit terbanyak (aggregate)
$result = $db->query("SELECT riwayat_penyakit, COUNT(*) as total
FROM data_kemiskinan
WHERE riwayat_penyakit IS NOT NULL AND riwayat_penyakit != ''
GROUP BY riwayat_penyakit
ORDER BY total DESC LIMIT 10");
$penyakit = [];
while ($row = $result->fetch_assoc()) {
$row['total'] = (int)$row['total'];
$penyakit[] = $row;
}
$stats['penyakit_terbanyak'] = $penyakit;
echo json_encode($stats);
$db->close();
exit();
}
// ── LAPORAN TERBARU ────────────────────────────────────────
if ($action === 'laporan_terbaru') {
$limit = isset($_GET['limit']) ? min(50, max(1, (int)$_GET['limit'])) : 20;
$result = $db->query("SELECT id, nama_pelapor, nik, no_wa, deskripsi, latitude, longitude, status, alasan_tolak, created_at
FROM laporan_masyarakat ORDER BY created_at DESC LIMIT $limit");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$rows[] = $row;
}
echo json_encode($rows);
$db->close();
exit();
}
// ── DATA KEMISKINAN UNTUK TABEL ────────────────────────────
if ($action === 'kemiskinan') {
$result = $db->query("SELECT id, nama_kk, nik, kategori, status_bantuan, jumlah_kk, alamat, pendidikan, riwayat_penyakit, latitude, longitude
FROM data_kemiskinan ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['jumlah_kk'] = (int)$row['jumlah_kk'];
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$rows[] = $row;
}
echo json_encode($rows);
$db->close();
exit();
}
// ── HEATMAP DATA ───────────────────────────────────────────
if ($action === 'heatmap') {
$result = $db->query("SELECT latitude, longitude, kategori FROM data_kemiskinan");
$points = [];
while ($row = $result->fetch_assoc()) {
$intensity = 0.5;
if ($row['kategori'] === 'Sangat Miskin') $intensity = 1.0;
else if ($row['kategori'] === 'Miskin') $intensity = 0.7;
$points[] = [(float)$row['latitude'], (float)$row['longitude'], $intensity];
}
echo json_encode($points);
$db->close();
exit();
}
http_response_code(400);
echo json_encode(['error' => 'Action tidak dikenali. Gunakan: stats, laporan_terbaru, kemiskinan, heatmap']);
?>
+165
View File
@@ -0,0 +1,165 @@
<?php
// ============================================================
// API DATA JALAN (Polyline/LineString)
// File: api_jalan.php
// Method: GET, POST, PUT, DELETE
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
// ---- GET: Ambil semua data atau satu data ----
if ($method === 'GET') {
$db = getDB();
if ($id) {
// Get single
$stmt = $db->prepare("SELECT id, nama_jalan, status, panjang_m, geojson FROM data_jalan WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
$result['geojson'] = json_decode($result['geojson']); // decode jadi object
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
// Get all
$result = $db->query("SELECT id, nama_jalan, status, panjang_m, geojson FROM data_jalan ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['geojson'] = json_decode($row['geojson']);
$rows[] = $row;
}
echo json_encode($rows);
}
$db->close();
exit();
}
// ---- POST: Tambah data jalan baru ----
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['nama_jalan']) || empty($input['status']) || empty($input['geojson'])) {
http_response_code(400);
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: nama_jalan, status, geojson']);
exit();
}
$nama_jalan = trim($input['nama_jalan']);
$status = $input['status'];
$panjang_m = isset($input['panjang_m']) ? (float)$input['panjang_m'] : 0;
$geojson = json_encode($input['geojson']); // simpan sebagai string JSON
// Validasi status
$valid_status = ['Nasional', 'Provinsi', 'Kabupaten'];
if (!in_array($status, $valid_status)) {
http_response_code(400);
echo json_encode(['error' => 'Status tidak valid. Pilih: Nasional, Provinsi, atau Kabupaten']);
exit();
}
$db = getDB();
$stmt = $db->prepare("INSERT INTO data_jalan (nama_jalan, status, panjang_m, geojson) VALUES (?, ?, ?, ?)");
$stmt->bind_param('ssds', $nama_jalan, $status, $panjang_m, $geojson);
if ($stmt->execute()) {
$new_id = $db->insert_id;
http_response_code(201);
echo json_encode([
'message' => 'Data jalan berhasil disimpan',
'id' => $new_id,
'nama_jalan'=> $nama_jalan,
'status' => $status,
'panjang_m' => $panjang_m
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- PUT: Update data jalan ----
if ($method === 'PUT') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk update']);
exit();
}
$input = json_decode(file_get_contents('php://input'), true);
$db = getDB();
// Cek apakah hanya update geometri (saat edit di peta) atau full update
if (isset($input['geojson']) && !isset($input['nama_jalan'])) {
// Update hanya geometri & panjang (drag/edit di peta)
$geojson = json_encode($input['geojson']);
$panjang_m = isset($input['panjang_m']) ? (float)$input['panjang_m'] : 0;
$stmt = $db->prepare("UPDATE data_jalan SET geojson = ?, panjang_m = ? WHERE id = ?");
$stmt->bind_param('sdi', $geojson, $panjang_m, $id);
} else {
// Full update (edit atribut + geometri)
$nama_jalan = trim($input['nama_jalan'] ?? '');
$status = $input['status'] ?? '';
$panjang_m = isset($input['panjang_m']) ? (float)$input['panjang_m'] : 0;
$geojson = json_encode($input['geojson'] ?? []);
$valid_status = ['Nasional', 'Provinsi', 'Kabupaten'];
if (!in_array($status, $valid_status)) {
http_response_code(400);
echo json_encode(['error' => 'Status tidak valid']);
exit();
}
$stmt = $db->prepare("UPDATE data_jalan SET nama_jalan = ?, status = ?, panjang_m = ?, geojson = ? WHERE id = ?");
$stmt->bind_param('ssdsi', $nama_jalan, $status, $panjang_m, $geojson, $id);
}
if ($stmt->execute()) {
echo json_encode(['message' => 'Data jalan berhasil diperbarui', 'id' => $id]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal memperbarui data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- DELETE: Hapus data jalan ----
if ($method === 'DELETE') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk hapus']);
exit();
}
$db = getDB();
$stmt = $db->prepare("DELETE FROM data_jalan WHERE id = ?");
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo json_encode(['message' => 'Data jalan berhasil dihapus', 'id' => $id]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menghapus data: ' . $stmt->error]);
}
$db->close();
exit();
}
// Method tidak dikenal
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
?>
+192
View File
@@ -0,0 +1,192 @@
<?php
// ============================================================
// API DATA KEMISKINAN (Point / Marker)
// File: api_kemiskinan.php
// Fields: nama_kk, jumlah_kk, alamat, latitude, longitude
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
// ---- GET: Ambil semua data atau satu data ----
if ($method === 'GET') {
$db = getDB();
if ($id) {
$stmt = $db->prepare("SELECT id, nik, nama_kk, jumlah_kk, alamat, latitude, longitude, status_bantuan, kategori, skor_penghasilan, skor_rumah, skor_makan, tanggal_lahir, pendidikan, riwayat_penyakit FROM data_kemiskinan WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
// Cast tipe
$result['jumlah_kk'] = (int)$result['jumlah_kk'];
$result['latitude'] = (float)$result['latitude'];
$result['longitude'] = (float)$result['longitude'];
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data kemiskinan tidak ditemukan']);
}
} else {
$result = $db->query("SELECT id, nik, nama_kk, jumlah_kk, alamat, latitude, longitude, status_bantuan, kategori, skor_penghasilan, skor_rumah, skor_makan, tanggal_lahir, pendidikan, riwayat_penyakit FROM data_kemiskinan ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['jumlah_kk'] = (int)$row['jumlah_kk'];
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$rows[] = $row;
}
echo json_encode($rows);
}
$db->close();
exit();
}
// ---- POST: Tambah data kemiskinan baru ----
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['nama_kk']) || !isset($input['jumlah_kk']) || empty($input['alamat'])
|| !isset($input['latitude']) || !isset($input['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: nama_kk, jumlah_kk, alamat, latitude, longitude']);
exit();
}
$nama_kk = trim($input['nama_kk']);
$nik = trim($input['nik'] ?? '');
$jumlah_kk = max(1, (int)$input['jumlah_kk']);
$alamat = trim($input['alamat']);
$latitude = (float)$input['latitude'];
$longitude = (float)$input['longitude'];
$tanggal_lahir = isset($input['tanggal_lahir']) && $input['tanggal_lahir'] !== '' ? $input['tanggal_lahir'] : null;
$pendidikan = isset($input['pendidikan']) ? trim($input['pendidikan']) : null;
$riwayat_penyakit = isset($input['riwayat_penyakit']) ? trim($input['riwayat_penyakit']) : null;
$kategori = isset($input['kategori']) ? trim($input['kategori']) : 'Miskin';
$skor_penghasilan = isset($input['skor_penghasilan']) ? (int)$input['skor_penghasilan'] : 0;
$skor_rumah = isset($input['skor_rumah']) ? (int)$input['skor_rumah'] : 0;
$skor_makan = isset($input['skor_makan']) ? (int)$input['skor_makan'] : 0;
$db = getDB();
$stmt = $db->prepare("INSERT INTO data_kemiskinan (nama_kk, nik, jumlah_kk, alamat, latitude, longitude, kategori, skor_penghasilan, skor_rumah, skor_makan, tanggal_lahir, pendidikan, riwayat_penyakit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param('ssisddsiiisss', $nama_kk, $nik, $jumlah_kk, $alamat, $latitude, $longitude, $kategori, $skor_penghasilan, $skor_rumah, $skor_makan, $tanggal_lahir, $pendidikan, $riwayat_penyakit);
if ($stmt->execute()) {
$new_id = $db->insert_id;
http_response_code(201);
echo json_encode([
'message' => 'Data kemiskinan berhasil disimpan',
'id' => $new_id,
'nama_kk' => $nama_kk,
'nik' => $nik,
'jumlah_kk' => $jumlah_kk,
'alamat' => $alamat,
'latitude' => $latitude,
'longitude' => $longitude,
'status_bantuan' => 'Belum',
'kategori' => $kategori,
'skor_penghasilan' => $skor_penghasilan,
'skor_rumah' => $skor_rumah,
'skor_makan' => $skor_makan,
'tanggal_lahir' => $tanggal_lahir,
'pendidikan' => $pendidikan,
'riwayat_penyakit' => $riwayat_penyakit
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- PUT: Update data kemiskinan ----
if ($method === 'PUT') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk update']);
exit();
}
$input = json_decode(file_get_contents('php://input'), true);
$db = getDB();
// Update posisi saja (drag marker)
if (isset($input['latitude']) && isset($input['longitude']) && !isset($input['nama_kk'])) {
$latitude = (float)$input['latitude'];
$longitude = (float)$input['longitude'];
$stmt = $db->prepare("UPDATE data_kemiskinan SET latitude = ?, longitude = ? WHERE id = ?");
$stmt->bind_param('ddi', $latitude, $longitude, $id);
} else {
// Full update
$nama_kk = trim($input['nama_kk'] ?? '');
$nik = trim($input['nik'] ?? '');
$jumlah_kk = max(1, (int)($input['jumlah_kk'] ?? 1));
$alamat = trim($input['alamat'] ?? '');
$latitude = (float)($input['latitude'] ?? 0);
$longitude = (float)($input['longitude'] ?? 0);
$tanggal_lahir = isset($input['tanggal_lahir']) && $input['tanggal_lahir'] !== '' ? $input['tanggal_lahir'] : null;
$pendidikan = isset($input['pendidikan']) ? trim($input['pendidikan']) : null;
$riwayat_penyakit = isset($input['riwayat_penyakit']) ? trim($input['riwayat_penyakit']) : null;
$kategori = isset($input['kategori']) ? trim($input['kategori']) : 'Miskin';
$skor_penghasilan = isset($input['skor_penghasilan']) ? (int)$input['skor_penghasilan'] : 0;
$skor_rumah = isset($input['skor_rumah']) ? (int)$input['skor_rumah'] : 0;
$skor_makan = isset($input['skor_makan']) ? (int)$input['skor_makan'] : 0;
if (empty($nama_kk) || empty($alamat)) {
http_response_code(400);
echo json_encode(['error' => 'nama_kk dan alamat wajib diisi']);
exit();
}
$stmt = $db->prepare("UPDATE data_kemiskinan SET nama_kk = ?, nik = ?, jumlah_kk = ?, alamat = ?, latitude = ?, longitude = ?, kategori = ?, skor_penghasilan = ?, skor_rumah = ?, skor_makan = ?, tanggal_lahir = ?, pendidikan = ?, riwayat_penyakit = ? WHERE id = ?");
$stmt->bind_param('ssisddsiiisssi', $nama_kk, $nik, $jumlah_kk, $alamat, $latitude, $longitude, $kategori, $skor_penghasilan, $skor_rumah, $skor_makan, $tanggal_lahir, $pendidikan, $riwayat_penyakit, $id);
}
if ($stmt->execute()) {
echo json_encode(['message' => 'Data kemiskinan berhasil diperbarui', 'id' => $id]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal memperbarui data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- DELETE: Hapus data kemiskinan ----
if ($method === 'DELETE') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk hapus']);
exit();
}
$db = getDB();
$stmt = $db->prepare("DELETE FROM data_kemiskinan WHERE id = ?");
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo json_encode(['message' => 'Data kemiskinan berhasil dihapus', 'id' => $id]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menghapus data: ' . $stmt->error]);
}
$db->close();
exit();
}
// Method tidak dikenal
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
?>
+218
View File
@@ -0,0 +1,218 @@
<?php
// ============================================================
// API LAPORAN MASYARAKAT
// File: api_lapor.php
// Fields: nama_pelapor, nik, no_wa, deskripsi, latitude, longitude, status
// Status: Pending | Diproses | Diverifikasi | Ditolak | Selesai
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
// ---- GET: Ambil laporan ----
if ($method === 'GET') {
$db = getDB();
if ($id) {
$stmt = $db->prepare("SELECT id, nama_pelapor, nik, no_wa, deskripsi, latitude, longitude, status, alasan_tolak, created_at, tanggal_lahir, pendidikan, riwayat_penyakit, skor_penghasilan, skor_rumah, skor_makan, jumlah_kk FROM laporan_masyarakat WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
$result['latitude'] = (float)$result['latitude'];
$result['longitude'] = (float)$result['longitude'];
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(['error' => 'Laporan tidak ditemukan']);
}
} else {
$result = $db->query("SELECT id, nama_pelapor, nik, no_wa, deskripsi, latitude, longitude, status, alasan_tolak, created_at, tanggal_lahir, pendidikan, riwayat_penyakit, skor_penghasilan, skor_rumah, skor_makan, jumlah_kk FROM laporan_masyarakat ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$rows[] = $row;
}
echo json_encode($rows);
}
$db->close();
exit();
}
// ---- POST: Tambah laporan baru ----
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['nama_pelapor']) || empty($input['deskripsi']) || !isset($input['latitude']) || !isset($input['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: nama_pelapor, deskripsi, latitude, longitude']);
exit();
}
$nama = trim($input['nama_pelapor']);
$nik = isset($input['nik']) && $input['nik'] ? trim($input['nik']) : null;
$no_wa = isset($input['no_wa']) && $input['no_wa'] ? trim($input['no_wa']) : null;
$desk = trim($input['deskripsi']);
$lat = (float)$input['latitude'];
$lng = (float)$input['longitude'];
// New Fields
$tgl_lahir = isset($input['tanggal_lahir']) && $input['tanggal_lahir'] ? $input['tanggal_lahir'] : null;
$pdd = isset($input['pendidikan']) && $input['pendidikan'] ? $input['pendidikan'] : null;
$pny = isset($input['riwayat_penyakit']) && $input['riwayat_penyakit'] ? $input['riwayat_penyakit'] : null;
$sp = isset($input['skor_penghasilan']) ? (int)$input['skor_penghasilan'] : null;
$sr = isset($input['skor_rumah']) ? (int)$input['skor_rumah'] : null;
$sm = isset($input['skor_makan']) ? (int)$input['skor_makan'] : null;
$jml = isset($input['jumlah_kk']) ? (int)$input['jumlah_kk'] : 1;
// Validasi NIK 16 digit jika diisi
if ($nik && strlen($nik) !== 16) {
http_response_code(400);
echo json_encode(['error' => 'NIK harus 16 digit']);
exit();
}
$db = getDB();
$stmt = $db->prepare("INSERT INTO laporan_masyarakat (nama_pelapor, nik, no_wa, deskripsi, latitude, longitude, tanggal_lahir, pendidikan, riwayat_penyakit, skor_penghasilan, skor_rumah, skor_makan, jumlah_kk) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param('ssssddsssiiii', $nama, $nik, $no_wa, $desk, $lat, $lng, $tgl_lahir, $pdd, $pny, $sp, $sr, $sm, $jml);
if ($stmt->execute()) {
$new_id = $db->insert_id;
http_response_code(201);
echo json_encode([
'message' => 'Laporan berhasil dikirim',
'id' => $new_id,
'status' => 'Pending'
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan laporan: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- PUT: Update status laporan ----
if ($method === 'PUT') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID wajib disertakan untuk update']);
exit();
}
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['status'])) {
http_response_code(400);
echo json_encode(['error' => 'Status wajib diisi (Pending/Diproses/Diverifikasi/Ditolak/Selesai)']);
exit();
}
$status = $input['status'];
$action = isset($input['action']) ? $input['action'] : null;
$alasan_tolak = isset($input['alasan_tolak']) ? trim($input['alasan_tolak']) : null;
$db = getDB();
// === VERIFIKASI: Konversi laporan menjadi data kemiskinan resmi ===
if ($action === 'verify' && $status === 'Diverifikasi') {
// Ambil data laporan dulu
$stmtGet = $db->prepare("SELECT * FROM laporan_masyarakat WHERE id = ?");
$stmtGet->bind_param('i', $id);
$stmtGet->execute();
$laporan = $stmtGet->get_result()->fetch_assoc();
if (!$laporan) {
http_response_code(404);
echo json_encode(['error' => 'Laporan tidak ditemukan']);
$db->close();
exit();
}
$db->begin_transaction();
try {
// Hitung kategori dari skor (sama dengan logika frontend)
$sp = (int)$laporan['skor_penghasilan'];
$sr = (int)$laporan['skor_rumah'];
$sm = (int)$laporan['skor_makan'];
$total_skor = $sp + $sr + $sm;
$kategori = 'Miskin';
if ($total_skor >= 7) {
$kategori = 'Sangat Miskin';
} else if ($total_skor >= 4) {
$kategori = 'Miskin';
} else {
$kategori = 'Hampir Miskin';
}
// 1. Masukkan ke data_kemiskinan sebagai data resmi
$stmtInsert = $db->prepare(
"INSERT INTO data_kemiskinan (nama_kk, nik, alamat, latitude, longitude, kategori, status_bantuan, tanggal_lahir, pendidikan, riwayat_penyakit, skor_penghasilan, skor_rumah, skor_makan, jumlah_kk)
VALUES (?, ?, ?, ?, ?, ?, 'Belum', ?, ?, ?, ?, ?, ?, ?)"
);
$alamat = $laporan['deskripsi']; // Use deskripsi directly as address/notes
$stmtInsert->bind_param('sssddssssiiii',
$laporan['nama_pelapor'],
$laporan['nik'],
$alamat,
$laporan['latitude'],
$laporan['longitude'],
$kategori,
$laporan['tanggal_lahir'],
$laporan['pendidikan'],
$laporan['riwayat_penyakit'],
$laporan['skor_penghasilan'],
$laporan['skor_rumah'],
$laporan['skor_makan'],
$laporan['jumlah_kk']
);
$stmtInsert->execute();
$new_kemiskinan_id = $db->insert_id;
// 2. Update status laporan menjadi Diverifikasi
$stmtUpdate = $db->prepare("UPDATE laporan_masyarakat SET status = 'Diverifikasi' WHERE id = ?");
$stmtUpdate->bind_param('i', $id);
$stmtUpdate->execute();
$db->commit();
echo json_encode([
'message' => 'Laporan diverifikasi dan data kemiskinan resmi telah dibuat',
'id_kemiskinan' => $new_kemiskinan_id
]);
} catch (Exception $e) {
$db->rollback();
http_response_code(500);
echo json_encode(['error' => 'Gagal memverifikasi: ' . $e->getMessage()]);
}
$db->close();
exit();
}
// === TOLAK: Update status + alasan ===
if ($status === 'Ditolak') {
$stmt = $db->prepare("UPDATE laporan_masyarakat SET status = ?, alasan_tolak = ? WHERE id = ?");
$stmt->bind_param('ssi', $status, $alasan_tolak, $id);
} else {
// === Update status biasa ===
$stmt = $db->prepare("UPDATE laporan_masyarakat SET status = ? WHERE id = ?");
$stmt->bind_param('si', $status, $id);
}
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo json_encode(['message' => 'Status laporan berhasil diupdate']);
} else {
http_response_code(404);
echo json_encode(['error' => 'Laporan tidak ditemukan atau tidak ada perubahan']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal mengupdate laporan']);
}
$db->close();
exit();
}
?>
+182
View File
@@ -0,0 +1,182 @@
<?php
// ============================================================
// API DATA RUMAH IBADAH (Point / Marker + Radius Buffer)
// File: api_masjid.php
// Fields: nama_masjid, jenis, pic_masjid, alamat, radius_m (100-5000), latitude, longitude
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
// ---- GET: Ambil semua data atau satu data ----
if ($method === 'GET') {
$db = getDB();
if ($id) {
$stmt = $db->prepare("SELECT id, nama_masjid, jenis, pic_masjid, alamat, radius_m, latitude, longitude FROM data_masjid WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
$result['radius_m'] = (int)$result['radius_m'];
$result['latitude'] = (float)$result['latitude'];
$result['longitude'] = (float)$result['longitude'];
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data masjid tidak ditemukan']);
}
} else {
$result = $db->query("SELECT id, nama_masjid, jenis, pic_masjid, alamat, radius_m, latitude, longitude FROM data_masjid ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['radius_m'] = (int)$row['radius_m'];
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$rows[] = $row;
}
echo json_encode($rows);
}
$db->close();
exit();
}
// ---- POST: Tambah masjid baru ----
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['nama_masjid']) || empty($input['pic_masjid']) || empty($input['alamat'])
|| !isset($input['latitude']) || !isset($input['longitude'])) {
http_response_code(400);
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: nama_masjid, jenis, pic_masjid, alamat, latitude, longitude']);
exit();
}
$nama_masjid = trim($input['nama_masjid']);
$jenis = isset($input['jenis']) ? trim($input['jenis']) : 'Masjid';
$pic_masjid = trim($input['pic_masjid']);
$alamat = trim($input['alamat']);
$radius_m = isset($input['radius_m']) ? max(100, min(5000, (int)$input['radius_m'])) : 500;
$latitude = (float)$input['latitude'];
$longitude = (float)$input['longitude'];
$valid_jenis = ['Masjid','Gereja','Pura','Vihara','Klenteng'];
if (!in_array($jenis, $valid_jenis)) $jenis = 'Masjid';
$db = getDB();
$stmt = $db->prepare("INSERT INTO data_masjid (nama_masjid, jenis, pic_masjid, alamat, radius_m, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param('ssssidd', $nama_masjid, $jenis, $pic_masjid, $alamat, $radius_m, $latitude, $longitude);
if ($stmt->execute()) {
$new_id = $db->insert_id;
http_response_code(201);
echo json_encode([
'message' => 'Data ibadah berhasil disimpan',
'id' => $new_id,
'nama_masjid' => $nama_masjid,
'jenis' => $jenis,
'pic_masjid' => $pic_masjid,
'alamat' => $alamat,
'radius_m' => $radius_m,
'latitude' => $latitude,
'longitude' => $longitude
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- PUT: Update masjid ----
if ($method === 'PUT') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk update']);
exit();
}
$input = json_decode(file_get_contents('php://input'), true);
$db = getDB();
// Update posisi saja (drag marker)
if (isset($input['latitude']) && isset($input['longitude'])
&& !isset($input['nama_masjid']) && !isset($input['radius_m'])) {
$latitude = (float)$input['latitude'];
$longitude = (float)$input['longitude'];
$stmt = $db->prepare("UPDATE data_masjid SET latitude = ?, longitude = ? WHERE id = ?");
$stmt->bind_param('ddi', $latitude, $longitude, $id);
// Update radius saja (dari slider tanpa simpan form)
} elseif (isset($input['radius_m']) && !isset($input['nama_masjid'])) {
$radius_m = max(100, min(5000, (int)$input['radius_m']));
$stmt = $db->prepare("UPDATE data_masjid SET radius_m = ? WHERE id = ?");
$stmt->bind_param('ii', $radius_m, $id);
} else {
// Full update
$nama_masjid = trim($input['nama_masjid'] ?? '');
$jenis = trim($input['jenis'] ?? 'Masjid');
$pic_masjid = trim($input['pic_masjid'] ?? '');
$alamat = trim($input['alamat'] ?? '');
$radius_m = isset($input['radius_m']) ? max(100, min(5000, (int)$input['radius_m'])) : 500;
$latitude = (float)($input['latitude'] ?? 0);
$longitude = (float)($input['longitude'] ?? 0);
$valid_jenis = ['Masjid','Gereja','Pura','Vihara','Klenteng'];
if (!in_array($jenis, $valid_jenis)) $jenis = 'Masjid';
if (empty($nama_masjid) || empty($pic_masjid) || empty($alamat)) {
http_response_code(400);
echo json_encode(['error' => 'nama_masjid, pic_masjid, dan alamat wajib diisi']);
exit();
}
$stmt = $db->prepare("UPDATE data_masjid SET nama_masjid = ?, jenis = ?, pic_masjid = ?, alamat = ?, radius_m = ?, latitude = ?, longitude = ? WHERE id = ?");
$stmt->bind_param('sssiddi', $nama_masjid, $jenis, $pic_masjid, $alamat, $radius_m, $latitude, $longitude, $id);
}
if ($stmt->execute()) {
echo json_encode(['message' => 'Data masjid berhasil diperbarui', 'id' => $id]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal memperbarui data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- DELETE: Hapus masjid ----
if ($method === 'DELETE') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk hapus']);
exit();
}
$db = getDB();
$stmt = $db->prepare("DELETE FROM data_masjid WHERE id = ?");
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo json_encode(['message' => 'Data masjid berhasil dihapus', 'id' => $id]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menghapus data: ' . $stmt->error]);
}
$db->close();
exit();
}
// Method tidak dikenal
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
?>
+169
View File
@@ -0,0 +1,169 @@
<?php
// ============================================================
// API DATA PARSIL TANAH (Polygon)
// File: api_parsil.php
// Method: GET, POST, PUT, DELETE
// Status: SHM, HGB, HGU, HP
// ============================================================
require_once 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
// ---- GET: Ambil semua data atau satu data ----
if ($method === 'GET') {
$db = getDB();
if ($id) {
// Get single
$stmt = $db->prepare("SELECT id, no_parsil, pemilik, status, luas_m2, geojson FROM data_parsil WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
if ($result) {
$result['geojson'] = json_decode($result['geojson']);
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
// Get all
$result = $db->query("SELECT id, no_parsil, pemilik, status, luas_m2, geojson FROM data_parsil ORDER BY id DESC");
$rows = [];
while ($row = $result->fetch_assoc()) {
$row['geojson'] = json_decode($row['geojson']);
$rows[] = $row;
}
echo json_encode($rows);
}
$db->close();
exit();
}
// ---- POST: Tambah parsil tanah baru ----
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['no_parsil']) || empty($input['pemilik']) || empty($input['status']) || empty($input['geojson'])) {
http_response_code(400);
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: no_parsil, pemilik, status, geojson']);
exit();
}
$no_parsil = trim($input['no_parsil']);
$pemilik = trim($input['pemilik']);
$status = $input['status'];
$luas_m2 = isset($input['luas_m2']) ? (float)$input['luas_m2'] : 0;
$geojson = json_encode($input['geojson']);
// Validasi status
$valid_status = ['SHM', 'HGB', 'HGU', 'HP'];
if (!in_array($status, $valid_status)) {
http_response_code(400);
echo json_encode(['error' => 'Status tidak valid. Pilih: SHM, HGB, HGU, atau HP']);
exit();
}
$db = getDB();
$stmt = $db->prepare("INSERT INTO data_parsil (no_parsil, pemilik, status, luas_m2, geojson) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param('sssds', $no_parsil, $pemilik, $status, $luas_m2, $geojson);
if ($stmt->execute()) {
$new_id = $db->insert_id;
http_response_code(201);
echo json_encode([
'message' => 'Data parsil berhasil disimpan',
'id' => $new_id,
'no_parsil' => $no_parsil,
'pemilik' => $pemilik,
'status' => $status,
'luas_m2' => $luas_m2
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menyimpan data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- PUT: Update data parsil ----
if ($method === 'PUT') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk update']);
exit();
}
$input = json_decode(file_get_contents('php://input'), true);
$db = getDB();
// Cek apakah hanya update geometri (edit bentuk di peta)
if (isset($input['geojson']) && !isset($input['no_parsil'])) {
// Update geometri & luas
$geojson = json_encode($input['geojson']);
$luas_m2 = isset($input['luas_m2']) ? (float)$input['luas_m2'] : 0;
$stmt = $db->prepare("UPDATE data_parsil SET geojson = ?, luas_m2 = ? WHERE id = ?");
$stmt->bind_param('sdi', $geojson, $luas_m2, $id);
} else {
// Full update (edit semua atribut)
$no_parsil = trim($input['no_parsil'] ?? '');
$pemilik = trim($input['pemilik'] ?? '');
$status = $input['status'] ?? '';
$luas_m2 = isset($input['luas_m2']) ? (float)$input['luas_m2'] : 0;
$geojson = json_encode($input['geojson'] ?? []);
$valid_status = ['SHM', 'HGB', 'HGU', 'HP'];
if (!in_array($status, $valid_status)) {
http_response_code(400);
echo json_encode(['error' => 'Status tidak valid']);
exit();
}
$stmt = $db->prepare("UPDATE data_parsil SET no_parsil = ?, pemilik = ?, status = ?, luas_m2 = ?, geojson = ? WHERE id = ?");
$stmt->bind_param('sssdsi', $no_parsil, $pemilik, $status, $luas_m2, $geojson, $id);
}
if ($stmt->execute()) {
echo json_encode(['message' => 'Data parsil berhasil diperbarui', 'id' => $id]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal memperbarui data: ' . $stmt->error]);
}
$db->close();
exit();
}
// ---- DELETE: Hapus parsil tanah ----
if ($method === 'DELETE') {
if (!$id) {
http_response_code(400);
echo json_encode(['error' => 'ID diperlukan untuk hapus']);
exit();
}
$db = getDB();
$stmt = $db->prepare("DELETE FROM data_parsil WHERE id = ?");
$stmt->bind_param('i', $id);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo json_encode(['message' => 'Data parsil berhasil dihapus', 'id' => $id]);
} else {
http_response_code(404);
echo json_encode(['error' => 'Data tidak ditemukan']);
}
} else {
http_response_code(500);
echo json_encode(['error' => 'Gagal menghapus data: ' . $stmt->error]);
}
$db->close();
exit();
}
// Method tidak dikenal
http_response_code(405);
echo json_encode(['error' => 'Method tidak diizinkan']);
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
// ============================================================
// KONFIGURASI DATABASE
// File: config.php
// ============================================================
define('DB_HOST', 'localhost');
define('DB_USER', 'root'); // Sesuaikan dengan user MySQL Anda
define('DB_PASS', ''); // Sesuaikan dengan password MySQL Anda
define('DB_NAME', 'webgis_p3');
function getDB() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
http_response_code(500);
die(json_encode(['error' => 'Koneksi database gagal: ' . $conn->connect_error]));
}
$conn->set_charset('utf8mb4');
return $conn;
}
// Header CORS & JSON untuk semua response
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
?>
+474
View File
@@ -0,0 +1,474 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Dashboard Eksekutif Walikota WebGIS Poverty Mapping Kota Pontianak">
<title>Dashboard Walikota WebGIS Poverty Mapping</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bg-dark: #0a0e1a;
--bg-panel: #0f1422;
--bg-card: #141928;
--bg-card-hover: #1a2035;
--border: rgba(255,255,255,.07);
--accent-blue: #3b82f6;
--accent-green: #10b981;
--accent-orange: #f59e0b;
--accent-red: #ef4444;
--accent-purple: #8b5cf6;
--text: #e8edf5;
--text-sub: #8a96b0;
--text-muted: #4b5563;
--radius: 12px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: var(--bg-dark);
color: var(--text);
min-height: 100vh;
}
/* Header */
.header {
background: linear-gradient(135deg, #0f1422 0%, #0a0e1a 100%);
border-bottom: 1px solid var(--border);
padding: 16px 32px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.header-left { display: flex; align-items: center; gap: 14px; }
.header-left h1 { font-size: 18px; font-weight: 800; }
.header-left span.tag {
font-size: 10px; font-weight: 700; padding: 3px 10px;
background: rgba(249,115,22,.15); border: 1px solid rgba(249,115,22,.3);
color: #fb923c; border-radius: 20px; text-transform: uppercase; letter-spacing: .5px;
}
.header-right { display: flex; align-items: center; gap: 14px; }
.user-info-hdr { text-align: right; }
.user-info-hdr .name { font-size: 13px; font-weight: 700; }
.user-info-hdr .role { font-size: 10px; color: #fb923c; font-weight: 600; }
.btn-back, .btn-logout-d {
padding: 7px 14px; border-radius: 8px; font-size: 11px; font-weight: 600;
cursor: pointer; border: none; font-family: 'Inter', sans-serif; transition: all .2s;
}
.btn-back {
background: rgba(59,130,246,.12); border: 1px solid rgba(59,130,246,.3); color: #60a5fa;
text-decoration: none;
}
.btn-back:hover { background: rgba(59,130,246,.22); }
.btn-logout-d {
background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.3); color: #f87171;
}
.btn-logout-d:hover { background: rgba(239,68,68,.22); }
/* Main Grid */
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 24px 32px 48px;
display: flex;
flex-direction: column;
gap: 24px;
}
/* KPI Cards Row */
.kpi-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 16px;
}
.kpi-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
position: relative;
overflow: hidden;
}
.kpi-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
}
.kpi-card:nth-child(1)::before { background: var(--accent-red); }
.kpi-card:nth-child(2)::before { background: var(--accent-orange); }
.kpi-card:nth-child(3)::before { background: var(--accent-green); }
.kpi-card:nth-child(4)::before { background: var(--accent-purple); }
.kpi-card:nth-child(5)::before { background: var(--accent-blue); }
.kpi-icon { font-size: 28px; margin-bottom: 10px; }
.kpi-value { font-size: 32px; font-weight: 800; line-height: 1; }
.kpi-label { font-size: 11px; color: var(--text-sub); margin-top: 6px; font-weight: 500; }
.kpi-sub { font-size: 10px; color: var(--text-muted); margin-top: 4px; }
/* Progress Bar */
.progress-bar {
width: 100%; height: 6px; background: rgba(255,255,255,.06);
border-radius: 3px; margin-top: 12px; overflow: hidden;
}
.progress-fill {
height: 100%; border-radius: 3px; transition: width .6s ease;
}
/* Two column layout */
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
/* Sections */
.section {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.section-head {
padding: 16px 20px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.section-head h2 {
font-size: 14px; font-weight: 700;
}
.section-body {
padding: 16px 20px;
}
/* Kategori bars */
.cat-row {
display: flex; align-items: center; gap: 12px;
padding: 10px 0; border-bottom: 1px solid var(--border);
}
.cat-row:last-child { border-bottom: none; }
.cat-label { width: 120px; font-size: 12px; font-weight: 600; }
.cat-bar-wrap { flex: 1; }
.cat-bar {
height: 20px; border-radius: 4px; position: relative; overflow: hidden;
background: rgba(255,255,255,.04);
}
.cat-fill {
height: 100%; border-radius: 4px; transition: width .6s; display: flex;
align-items: center; padding-left: 8px; font-size: 10px; font-weight: 700;
}
.cat-count { width: 50px; text-align: right; font-size: 13px; font-weight: 700; }
/* Laporan list */
.laporan-item {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 12px;
}
.laporan-item:last-child { border-bottom: none; }
.laporan-info { flex: 1; }
.laporan-nama { font-weight: 600; margin-bottom: 2px; }
.laporan-desc { color: var(--text-sub); font-size: 11px; }
.status-badge {
font-size: 10px; font-weight: 700; padding: 3px 8px;
border-radius: 4px; white-space: nowrap;
}
/* Map */
#mini-map {
width: 100%;
height: 350px;
border-radius: 0 0 var(--radius) var(--radius);
}
/* Penyakit Table */
.simple-table { width: 100%; border-collapse: collapse; }
.simple-table th, .simple-table td {
padding: 8px 12px; text-align: left; font-size: 12px;
border-bottom: 1px solid var(--border);
}
.simple-table th {
color: var(--text-muted); font-weight: 600; text-transform: uppercase;
font-size: 10px; letter-spacing: .5px;
}
/* Export buttons */
.btn-export-d {
padding: 6px 14px; border-radius: 6px; font-size: 11px; font-weight: 600;
cursor: pointer; border: none; font-family: 'Inter', sans-serif;
background: rgba(59,130,246,.12); border: 1px solid rgba(59,130,246,.3);
color: #60a5fa; transition: all .2s;
}
.btn-export-d:hover { background: rgba(59,130,246,.22); }
/* Loading */
.loading { text-align: center; padding: 30px; color: var(--text-muted); font-size: 12px; }
/* Scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.section-body-scroll { max-height: 320px; overflow-y: auto; }
@media (max-width: 1024px) {
.kpi-row { grid-template-columns: repeat(3, 1fr); }
.two-col { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.kpi-row { grid-template-columns: repeat(2, 1fr); }
.dashboard { padding: 16px; }
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<div class="header-left">
<span style="font-size:24px">👑</span>
<h1>Dashboard Eksekutif</h1>
<span class="tag">Walikota Pontianak</span>
</div>
<div class="header-right">
<div class="user-info-hdr">
<div class="name" id="hdr-name"></div>
<div class="role">👑 Walikota</div>
</div>
<a href="index.html" class="btn-back">🗺️ Lihat Peta</a>
<button class="btn-logout-d" onclick="logout()">🚪 Keluar</button>
</div>
</div>
<!-- Dashboard Content -->
<div class="dashboard">
<!-- KPI Row -->
<div class="kpi-row">
<div class="kpi-card">
<div class="kpi-icon">🏚️</div>
<div class="kpi-value" id="kpi-total" style="color:var(--accent-red)"></div>
<div class="kpi-label">Total Warga Miskin</div>
<div class="kpi-sub" id="kpi-total-sub">Memuat...</div>
</div>
<div class="kpi-card">
<div class="kpi-icon"></div>
<div class="kpi-value" id="kpi-sudah" style="color:var(--accent-green)"></div>
<div class="kpi-label">Sudah Dibantu</div>
<div class="progress-bar"><div class="progress-fill" id="prog-sudah" style="width:0%; background:var(--accent-green)"></div></div>
<div class="kpi-sub" id="kpi-sudah-sub">0%</div>
</div>
<div class="kpi-card">
<div class="kpi-icon"></div>
<div class="kpi-value" id="kpi-belum" style="color:var(--accent-orange)"></div>
<div class="kpi-label">Belum Dibantu</div>
<div class="kpi-sub" id="kpi-belum-sub">Memuat...</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">🕌</div>
<div class="kpi-value" id="kpi-ibadah" style="color:var(--accent-purple)"></div>
<div class="kpi-label">Rumah Ibadah Aktif</div>
<div class="kpi-sub" id="kpi-ibadah-sub">Memuat...</div>
</div>
<div class="kpi-card">
<div class="kpi-icon">🎁</div>
<div class="kpi-value" id="kpi-bantuan" style="color:var(--accent-blue)"></div>
<div class="kpi-label">Total Bantuan Tercatat</div>
<div class="kpi-sub">Seluruh riwayat</div>
</div>
</div>
<!-- Distribusi Kategori + Laporan Terbaru -->
<div class="two-col">
<!-- Distribusi Kategori -->
<div class="section">
<div class="section-head">
<h2>📊 Distribusi Kategori Kemiskinan</h2>
<button class="btn-export-d" onclick="exportCSV()">📥 Export CSV</button>
</div>
<div class="section-body" id="distribusi-body">
<div class="loading">⏳ Memuat data...</div>
</div>
</div>
<!-- Laporan Terbaru -->
<div class="section">
<div class="section-head">
<h2>📢 Laporan Masyarakat Terbaru</h2>
</div>
<div class="section-body section-body-scroll" id="laporan-body">
<div class="loading">⏳ Memuat data...</div>
</div>
</div>
</div>
<!-- Peta Heatmap + Riwayat Penyakit -->
<div class="two-col">
<!-- Peta Heatmap -->
<div class="section">
<div class="section-head">
<h2>🔥 Heatmap Distribusi Kemiskinan</h2>
</div>
<div id="mini-map"></div>
</div>
<!-- Penyakit Terbanyak -->
<div class="section">
<div class="section-head">
<h2>🏥 Riwayat Penyakit Terbanyak</h2>
</div>
<div class="section-body" id="penyakit-body">
<div class="loading">⏳ Memuat data...</div>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js"></script>
<script>
// ── Auth Check ──
const currentUser = JSON.parse(sessionStorage.getItem('webgis_user') || 'null');
if (!currentUser || currentUser.role !== 'walikota') {
// Bukan walikota → redirect
window.location.replace('login.html');
}
document.getElementById('hdr-name').textContent = currentUser.nama || currentUser.username || 'Walikota';
function logout() {
if (!confirm('Yakin ingin keluar?')) return;
sessionStorage.removeItem('webgis_user');
window.location.replace('login.html?logout=1');
}
const API = 'api_dashboard.php';
// ── Load Stats ──
fetch(`${API}?action=stats`).then(r => r.json()).then(stats => {
// KPI
document.getElementById('kpi-total').textContent = stats.total_warga;
document.getElementById('kpi-total-sub').textContent = `Data terverifikasi`;
document.getElementById('kpi-sudah').textContent = stats.total_sudah;
document.getElementById('kpi-sudah-sub').textContent = `${stats.persen_sudah}% dari total`;
document.getElementById('prog-sudah').style.width = `${stats.persen_sudah}%`;
document.getElementById('kpi-belum').textContent = stats.total_belum;
document.getElementById('kpi-belum-sub').textContent = `Perlu perhatian`;
document.getElementById('kpi-ibadah').textContent = stats.total_ibadah;
const ibadahTypes = stats.rumah_ibadah.map(i => `${i.jenis}: ${i.total}`).join(', ');
document.getElementById('kpi-ibadah-sub').textContent = ibadahTypes || 'Belum ada data';
document.getElementById('kpi-bantuan').textContent = stats.total_bantuan;
// Distribusi Kategori
const distBody = document.getElementById('distribusi-body');
const cats = [
{ label: '🚨 Sangat Miskin', color: '#ef4444', key: 'Sangat Miskin' },
{ label: '⚠️ Miskin', color: '#f97316', key: 'Miskin' },
{ label: '💡 Hampir Miskin', color: '#eab308', key: 'Hampir Miskin' }
];
let distHTML = '';
cats.forEach(c => {
const found = stats.kategori.find(k => k.kategori === c.key);
const total = found ? found.total : 0;
const sudah = found ? found.sudah_dibantu : 0;
const belum = found ? found.belum_dibantu : 0;
const pct = stats.total_warga > 0 ? ((total / stats.total_warga) * 100).toFixed(1) : 0;
distHTML += `<div class="cat-row">
<div class="cat-label" style="color:${c.color}">${c.label}</div>
<div class="cat-bar-wrap">
<div class="cat-bar">
<div class="cat-fill" style="width:${pct}%; background:${c.color}; color:#fff; min-width:40px">${pct}%</div>
</div>
<div style="font-size:10px; color:var(--text-muted); margin-top:3px">Sudah: ${sudah} | Belum: ${belum}</div>
</div>
<div class="cat-count" style="color:${c.color}">${total}</div>
</div>`;
});
distBody.innerHTML = distHTML;
// Penyakit
const pBody = document.getElementById('penyakit-body');
if (stats.penyakit_terbanyak.length === 0) {
pBody.innerHTML = '<div class="loading">Belum ada data riwayat penyakit</div>';
} else {
let tbl = '<table class="simple-table"><thead><tr><th>Penyakit</th><th>Jumlah Warga</th></tr></thead><tbody>';
stats.penyakit_terbanyak.forEach((p, i) => {
tbl += `<tr><td style="font-weight:600">${p.riwayat_penyakit}</td><td style="color:var(--accent-red);font-weight:700">${p.total}</td></tr>`;
});
tbl += '</tbody></table>';
pBody.innerHTML = tbl;
}
}).catch(err => {
console.error('Gagal memuat statistik:', err);
});
// ── Load Laporan Terbaru ──
fetch(`${API}?action=laporan_terbaru&limit=15`).then(r => r.json()).then(data => {
const body = document.getElementById('laporan-body');
if (!data.length) {
body.innerHTML = '<div class="loading">Belum ada laporan masuk</div>';
return;
}
let html = '';
const statusColors = { 'Pending': '#eab308', 'Diproses': '#3b82f6', 'Diverifikasi': '#10b981', 'Ditolak': '#ef4444', 'Selesai': '#10b981' };
data.forEach(d => {
const sc = statusColors[d.status] || '#888';
html += `<div class="laporan-item">
<div class="laporan-info">
<div class="laporan-nama">${d.nama_pelapor}</div>
<div class="laporan-desc">${d.deskripsi.substring(0, 60)}${d.deskripsi.length > 60 ? '...' : ''}</div>
</div>
<span class="status-badge" style="background:${sc}22;color:${sc};border:1px solid ${sc}">${d.status}</span>
</div>`;
});
body.innerHTML = html;
});
// ── Mini Map + Heatmap ──
const miniMap = L.map('mini-map', { zoomControl: false }).setView([-0.0553, 109.3495], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OSM', maxZoom: 18
}).addTo(miniMap);
fetch(`${API}?action=heatmap`).then(r => r.json()).then(points => {
if (points.length) {
L.heatLayer(points, {
radius: 25, blur: 18, maxZoom: 15,
gradient: { 0.2: '#2563eb', 0.4: '#f59e0b', 0.6: '#f97316', 0.8: '#ef4444', 1: '#dc2626' }
}).addTo(miniMap);
}
});
// ── Export CSV ──
function exportCSV() {
fetch(`${API}?action=kemiskinan`).then(r => r.json()).then(data => {
if (!data.length) { alert('Tidak ada data'); return; }
const headers = ['ID','Nama KK','NIK','Kategori','Status Bantuan','Jumlah KK','Alamat','Pendidikan','Riwayat Penyakit','Lat','Lng'];
const rows = data.map(d => [
d.id, d.nama_kk, d.nik || '', d.kategori || '', d.status_bantuan || 'Belum',
d.jumlah_kk, '"' + (d.alamat || '').replace(/"/g, '""') + '"',
d.pendidikan || '', d.riwayat_penyakit || '',
d.latitude, d.longitude
]);
let csv = '\uFEFF' + headers.join(',') + '\n';
rows.forEach(r => csv += r.join(',') + '\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `laporan_kemiskinan_walikota_${new Date().toISOString().slice(0,10)}.csv`;
a.click();
URL.revokeObjectURL(url);
});
}
</script>
</body>
</html>
+88
View File
@@ -0,0 +1,88 @@
-- ============================================================
-- DATABASE: webgis_p3
-- Pertemuan 3 Layer Groups and Layers Control
-- Data SPBU dengan 2 Layer Group: Buka 24 Jam & Tidak 24 Jam
-- ============================================================
CREATE DATABASE IF NOT EXISTS webgis_p3
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE webgis_p3;
-- ---- Tabel Data Jalan (Polyline) ----
CREATE TABLE IF NOT EXISTS data_jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_jalan VARCHAR(200) NOT NULL,
status ENUM('Nasional','Provinsi','Kabupaten') NOT NULL DEFAULT 'Kabupaten',
panjang_m DOUBLE NOT NULL DEFAULT 0,
geojson LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---- Tabel Data Parsil Tanah (Polygon) ----
CREATE TABLE IF NOT EXISTS data_parsil (
id INT AUTO_INCREMENT PRIMARY KEY,
no_parsil VARCHAR(100) NOT NULL,
pemilik VARCHAR(200) NOT NULL,
status ENUM('SHM','HGB','HGU','HP') NOT NULL DEFAULT 'SHM',
luas_m2 DOUBLE NOT NULL DEFAULT 0,
geojson LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---- Tabel Data SPBU (Point/Marker) ----
-- buka_24_jam: 'Ya' → Layer Group "SPBU 24 Jam"
-- 'Tidak' → Layer Group "SPBU Tidak 24 Jam"
CREATE TABLE IF NOT EXISTS data_spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_spbu VARCHAR(200) NOT NULL,
no_wa VARCHAR(20) NOT NULL,
buka_24_jam ENUM('Ya','Tidak') NOT NULL DEFAULT 'Tidak',
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ---- Data Contoh SPBU (opsional, untuk testing) ----
INSERT INTO data_spbu (nama_spbu, no_wa, buka_24_jam, latitude, longitude) VALUES
('SPBU Jl. Ahmad Yani', '0812-1111-0001', 'Ya', -0.0400, 109.3200),
('SPBU Jl. Gajah Mada', '0812-1111-0002', 'Ya', -0.0520, 109.3450),
('SPBU Jl. Diponegoro', '0812-1111-0003', 'Tidak', -0.0610, 109.3600),
('SPBU Jl. Sutan Syahrir', '0812-1111-0004', 'Tidak', -0.0480, 109.3700),
('SPBU Jl. Kom. Yos Sudarso', '0812-1111-0005', 'Ya', -0.0350, 109.3550);
-- ============================================================
-- Tabel Data Kemiskinan (Point/Marker)
-- ============================================================
CREATE TABLE IF NOT EXISTS data_kemiskinan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_kk VARCHAR(200) NOT NULL, -- Nama kepala keluarga
jumlah_kk INT NOT NULL DEFAULT 1, -- Jumlah anggota dalam KK
alamat TEXT NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Tabel Data Rumah Ibadah (Point/Marker + Radius Buffer)
-- jenis: Masjid, Gereja, Pura, Vihara, Klenteng
-- radius_m: radius lingkaran buffer dalam meter (100 5000)
-- ============================================================
CREATE TABLE IF NOT EXISTS data_masjid (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_masjid VARCHAR(200) NOT NULL,
jenis ENUM('Masjid','Gereja','Pura','Vihara','Klenteng') NOT NULL DEFAULT 'Masjid',
pic_masjid VARCHAR(200) NOT NULL, -- Person In Charge
alamat TEXT NOT NULL,
radius_m INT NOT NULL DEFAULT 500, -- Radius buffer (100-5000 m)
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+132
View File
@@ -0,0 +1,132 @@
-- ============================================================
-- DATABASE LENGKAP: webgis_p3
-- WebGIS Poverty Mapping + Sistem Login (3 Role)
-- Import file ini ke Laragon phpMyAdmin
-- ============================================================
CREATE DATABASE IF NOT EXISTS webgis_p3
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE webgis_p3;
-- ── Tabel Data Jalan (Polyline) ────────────────────────────
CREATE TABLE IF NOT EXISTS data_jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_jalan VARCHAR(200) NOT NULL,
status ENUM('Nasional','Provinsi','Kabupaten') NOT NULL DEFAULT 'Kabupaten',
panjang_m DOUBLE NOT NULL DEFAULT 0,
geojson LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Tabel Data Parsil Tanah (Polygon) ─────────────────────
CREATE TABLE IF NOT EXISTS data_parsil (
id INT AUTO_INCREMENT PRIMARY KEY,
no_parsil VARCHAR(100) NOT NULL,
pemilik VARCHAR(200) NOT NULL,
status ENUM('SHM','HGB','HGU','HP') NOT NULL DEFAULT 'SHM',
luas_m2 DOUBLE NOT NULL DEFAULT 0,
geojson LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Tabel Data SPBU (Point/Marker) ────────────────────────
CREATE TABLE IF NOT EXISTS data_spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_spbu VARCHAR(200) NOT NULL,
no_wa VARCHAR(20) NOT NULL,
buka_24_jam ENUM('Ya','Tidak') NOT NULL DEFAULT 'Tidak',
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Data Contoh SPBU ──────────────────────────────────────
INSERT INTO data_spbu (nama_spbu, no_wa, buka_24_jam, latitude, longitude) VALUES
('SPBU Jl. Ahmad Yani', '0812-1111-0001', 'Ya', -0.0400, 109.3200),
('SPBU Jl. Gajah Mada', '0812-1111-0002', 'Ya', -0.0520, 109.3450),
('SPBU Jl. Diponegoro', '0812-1111-0003', 'Tidak', -0.0610, 109.3600),
('SPBU Jl. Sutan Syahrir', '0812-1111-0004', 'Tidak', -0.0480, 109.3700),
('SPBU Jl. Kom. Yos Sudarso', '0812-1111-0005', 'Ya', -0.0350, 109.3550);
-- ── Tabel Data Kemiskinan (Point/Marker) ──────────────────
CREATE TABLE IF NOT EXISTS data_kemiskinan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_kk VARCHAR(200) NOT NULL,
jumlah_kk INT NOT NULL DEFAULT 1,
alamat TEXT NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Tabel Data Rumah Ibadah (Point/Marker + Radius Buffer)
CREATE TABLE IF NOT EXISTS data_masjid (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_masjid VARCHAR(200) NOT NULL,
jenis ENUM('Masjid','Mushola','Gereja','Pura','Vihara','Klenteng') NOT NULL DEFAULT 'Masjid',
pic_masjid VARCHAR(200) NOT NULL,
alamat TEXT NOT NULL,
radius_m INT NOT NULL DEFAULT 500,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Tabel Users (Sistem Login 3 Role) ─────────────────────
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
nama_lengkap VARCHAR(200) NOT NULL,
role ENUM('admin','petugas','pengurus','walikota') NOT NULL,
password_hash VARCHAR(255) NOT NULL,
aktif TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_role (role),
INDEX idx_aktif (aktif)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Akun Demo ─────────────────────────────────────────────
-- Password di-hash menggunakan bcrypt (PHP password_hash)
-- admin → admin123
-- petugas → petugas123
-- pengurus → pengurus123
--
-- Hash ini sudah valid, digenerate dengan PASSWORD_DEFAULT PHP 8
INSERT INTO users (username, nama_lengkap, role, password_hash) VALUES
(
'admin',
'Administrator Sistem',
'admin',
'$2y$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
),
(
'petugas',
'Petugas Lapangan',
'petugas',
'$2y$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
),
(
'pengurus',
'Pengurus Rumah Ibadah',
'pengurus',
'$2y$12$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
),
(
'walikota',
'Walikota Pontianak',
'walikota',
'$2y$10$s0387VqXY8wBXYkVytEKxumpF0DmlBlE1z3usinr0nr1RYN/4BZw.'
);
-- !! CATATAN: Hash di atas adalah placeholder "password".
-- Setelah import, jalankan generate_hash.php untuk meng-update
-- hash dengan password yang benar (admin123, petugas123, pengurus123).
-- URL: http://localhost/WebGIS/pertemuan fix/generate_hash.php
+94
View File
@@ -0,0 +1,94 @@
<?php
// ============================================================
// generate_hash.php Generate bcrypt hash & insert akun demo
// Akses sekali: http://localhost/WebGIS/pertemuan fix/generate_hash.php
// HAPUS file ini setelah digunakan!
// ============================================================
require_once 'config.php';
// Daftar akun demo yang akan di-insert / update
$demoUsers = [
['username' => 'admin', 'nama' => 'Administrator Sistem', 'role' => 'admin', 'password' => 'admin123'],
['username' => 'pengurus', 'nama' => 'Pengurus Rumah Ibadah', 'role' => 'pengurus', 'password' => 'pengurus123'],
['username' => 'walikota', 'nama' => 'Walikota Pontianak', 'role' => 'walikota', 'password' => 'walikota123'],
];
$db = getDB();
$results = [];
foreach ($demoUsers as $u) {
$hash = password_hash($u['password'], PASSWORD_DEFAULT);
// INSERT or UPDATE
$stmt = $db->prepare(
"INSERT INTO users (username, nama_lengkap, role, password_hash)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
nama_lengkap = VALUES(nama_lengkap),
role = VALUES(role),
password_hash = VALUES(password_hash)"
);
$stmt->bind_param('ssss', $u['username'], $u['nama'], $u['role'], $hash);
if ($stmt->execute()) {
$results[] = [
'status' => '✅ OK',
'username' => $u['username'],
'role' => $u['role'],
'hash' => $hash
];
} else {
$results[] = [
'status' => '❌ ERROR: ' . $stmt->error,
'username' => $u['username'],
'role' => $u['role'],
'hash' => '-'
];
}
$stmt->close();
}
$db->close();
// Output sebagai HTML yang mudah dibaca
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Generate Hash WebGIS</title>
<style>
body { font-family: monospace; background:#111; color:#e2e8f0; padding:30px; }
h2 { color:#60a5fa; margin-bottom:20px; }
table { border-collapse:collapse; width:100%; }
th,td { border:1px solid #334155; padding:10px 14px; text-align:left; font-size:13px; }
th { background:#1e293b; color:#94a3b8; font-weight:700; }
.ok { color:#4ade80; }
.err { color:#f87171; }
.warn { background:#1c1917; border:1px solid #f59e0b; color:#fbbf24; padding:16px; border-radius:8px; margin-top:24px; }
</style>
</head>
<body>
<h2>🔑 Generate Hash Akun Demo WebGIS</h2>
<table>
<tr><th>Status</th><th>Username</th><th>Role</th><th>Password</th><th>Hash (bcrypt)</th></tr>
<?php foreach($results as $r): ?>
<tr>
<td class="<?= str_starts_with($r['status'],'✅') ? 'ok':'err' ?>"><?= $r['status'] ?></td>
<td><?= htmlspecialchars($r['username']) ?></td>
<td><?= htmlspecialchars($r['role']) ?></td>
<td><?= ($r['username'].'123') ?></td>
<td style="font-size:11px;word-break:break-all"><?= htmlspecialchars($r['hash']) ?></td>
</tr>
<?php endforeach; ?>
</table>
<div class="warn">
⚠️ <strong>PERHATIAN:</strong> File ini sudah tidak diperlukan. Hapus <code>generate_hash.php</code> setelah proses selesai untuk keamanan sistem.
</div>
<p style="margin-top:20px;color:#64748b">
Setelah berhasil, buka <a href="login.html" style="color:#60a5fa">login.html</a> dan coba login.
</p>
</body>
</html>
File diff suppressed because it is too large Load Diff
+846
View File
@@ -0,0 +1,846 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Login WebGIS Pemetaan Kemiskinan Berbasis Partisipasi Rumah Ibadah">
<title>Masuk WebGIS Poverty Mapping</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet">
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg: #0a0e1a;
--panel: #0f1422;
--card: #141928;
--border: rgba(255, 255, 255, .07);
--blue: #4f7cff;
--blue-dim: rgba(79, 124, 255, .12);
--green: #22d3a5;
--orange: #ff8c42;
--purple: #9b6dff;
--text: #e8edf5;
--muted: #5c6880;
--sub: #8a96b0;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 480px;
}
/* ── LEFT PANEL ── */
.left-panel {
position: relative;
overflow: hidden;
background: #080c18;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 48px;
}
.map-bg {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 70% 60% at 30% 40%, rgba(79, 124, 255, .18) 0%, transparent 65%),
radial-gradient(ellipse 50% 70% at 70% 70%, rgba(34, 211, 165, .1) 0%, transparent 60%),
radial-gradient(ellipse 40% 40% at 60% 20%, rgba(155, 109, 255, .08) 0%, transparent 55%);
}
/* Animated grid lines */
.grid-lines {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255, 255, 255, .025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, .025) 1px, transparent 1px);
background-size: 60px 60px;
animation: gridShift 20s linear infinite;
}
@keyframes gridShift {
from {
background-position: 0 0;
}
to {
background-position: 60px 60px;
}
}
/* Floating map dots */
.dot {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
.dot-1 {
width: 12px;
height: 12px;
background: var(--blue);
box-shadow: 0 0 20px var(--blue), 0 0 40px rgba(79, 124, 255, .4);
top: 32%;
left: 28%;
animation: pulseDot 3s ease-in-out infinite;
}
.dot-2 {
width: 8px;
height: 8px;
background: var(--green);
box-shadow: 0 0 15px var(--green);
top: 55%;
left: 55%;
animation: pulseDot 3s ease-in-out infinite 1s;
}
.dot-3 {
width: 10px;
height: 10px;
background: var(--orange);
box-shadow: 0 0 15px var(--orange);
top: 22%;
left: 60%;
animation: pulseDot 3s ease-in-out infinite 2s;
}
.dot-4 {
width: 7px;
height: 7px;
background: var(--purple);
box-shadow: 0 0 12px var(--purple);
top: 68%;
left: 35%;
animation: pulseDot 3s ease-in-out infinite .5s;
}
/* Connecting lines between dots */
.conn-lines {
position: absolute;
inset: 0;
overflow: hidden;
}
.conn-lines svg {
width: 100%;
height: 100%;
}
@keyframes pulseDot {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.4);
opacity: .6;
}
}
/* Ripple rings on dots */
.dot::after {
content: '';
position: absolute;
inset: -8px;
border-radius: 50%;
border: 1.5px solid currentColor;
opacity: 0;
animation: ripple 3s ease-in-out infinite;
}
.dot-1::after {
color: var(--blue);
}
.dot-2::after {
color: var(--green);
animation-delay: 1s;
}
.dot-3::after {
color: var(--orange);
animation-delay: 2s;
}
.dot-4::after {
color: var(--purple);
animation-delay: .5s;
}
@keyframes ripple {
0% {
inset: -4px;
opacity: .7;
}
100% {
inset: -24px;
opacity: 0;
}
}
.left-content {
position: relative;
z-index: 2;
}
.left-tag {
display: inline-flex;
align-items: center;
gap: 7px;
background: rgba(79, 124, 255, .15);
border: 1px solid rgba(79, 124, 255, .3);
border-radius: 20px;
padding: 5px 14px;
font-size: 11px;
font-weight: 600;
color: #7ba4ff;
letter-spacing: .5px;
text-transform: uppercase;
margin-bottom: 20px;
}
.left-tag-dot {
width: 6px;
height: 6px;
background: var(--blue);
border-radius: 50%;
box-shadow: 0 0 8px var(--blue);
animation: pulseDot 2s ease-in-out infinite;
}
.left-title {
font-size: clamp(28px, 3.5vw, 42px);
font-weight: 800;
line-height: 1.15;
letter-spacing: -.5px;
color: #fff;
margin-bottom: 16px;
}
.left-title em {
font-style: normal;
background: linear-gradient(90deg, var(--blue), var(--green));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.left-desc {
font-size: 14px;
color: var(--sub);
line-height: 1.7;
max-width: 400px;
margin-bottom: 32px;
}
.stat-row {
display: flex;
gap: 20px;
}
.stat {
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-val {
font-size: 24px;
font-weight: 800;
color: #fff;
}
.stat-lbl {
font-size: 11px;
color: var(--muted);
font-weight: 500;
}
.stat-sep {
width: 1px;
background: var(--border);
align-self: stretch;
}
/* ── RIGHT PANEL ── */
.right-panel {
background: var(--panel);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
justify-content: center;
padding: 48px 40px;
overflow-y: auto;
}
.logo-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 40px;
}
.logo-icon {
width: 44px;
height: 44px;
background: linear-gradient(135deg, #1a3a8f, var(--blue));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
box-shadow: 0 6px 20px rgba(79, 124, 255, .3);
flex-shrink: 0;
}
.logo-text {
display: flex;
flex-direction: column;
}
.logo-name {
font-size: 14px;
font-weight: 800;
color: var(--text);
letter-spacing: -.2px;
}
.logo-sub {
font-size: 11px;
color: var(--muted);
margin-top: 1px;
}
.form-title {
font-size: 26px;
font-weight: 800;
color: var(--text);
letter-spacing: -.4px;
margin-bottom: 6px;
}
.form-sub {
font-size: 13px;
color: var(--sub);
margin-bottom: 32px;
}
/* Role tabs */
.role-tabs {
display: flex;
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 4px;
gap: 4px;
margin-bottom: 28px;
}
.role-tab {
flex: 1;
padding: 8px 6px;
border: none;
border-radius: 7px;
background: transparent;
color: var(--muted);
font-family: inherit;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all .2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
}
.role-tab-icon {
font-size: 18px;
}
.role-tab-name {
font-size: 11px;
}
.role-tab:hover {
color: var(--text);
background: rgba(255, 255, 255, .04);
}
.role-tab.active {
background: var(--blue-dim);
color: var(--blue);
border: 1px solid rgba(79, 124, 255, .25);
}
.role-tab.active[data-role="pengurus"] {
color: var(--purple);
background: rgba(155, 109, 255, .1);
border-color: rgba(155, 109, 255, .25);
}
.role-tab.active[data-role="walikota"] {
color: var(--orange);
background: rgba(255, 140, 66, .1);
border-color: rgba(255, 140, 66, .25);
}
/* Fields */
.field {
margin-bottom: 18px;
}
.field-label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--sub);
margin-bottom: 7px;
letter-spacing: .3px;
}
.field-wrap {
position: relative;
}
.field-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
width: 16px;
text-align: center;
color: var(--muted);
font-size: 14px;
pointer-events: none;
}
.field-input {
width: 100%;
padding: 12px 14px 12px 42px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
font-family: inherit;
font-size: 13.5px;
outline: none;
transition: border-color .2s, box-shadow .2s;
}
.field-input:focus {
border-color: rgba(79, 124, 255, .5);
box-shadow: 0 0 0 3px rgba(79, 124, 255, .1);
}
.field-input::placeholder {
color: var(--muted);
font-size: 13px;
}
.pw-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--muted);
font-size: 14px;
cursor: pointer;
padding: 4px;
transition: color .2s;
}
.pw-toggle:hover {
color: var(--sub);
}
/* Error */
.error-msg {
display: none;
align-items: center;
gap: 8px;
background: rgba(239, 68, 68, .08);
border: 1px solid rgba(239, 68, 68, .2);
color: #fca5a5;
border-radius: 8px;
padding: 10px 14px;
font-size: 12.5px;
margin-bottom: 18px;
animation: shake .3s ease;
}
.error-msg.show {
display: flex;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
/* Login button */
.btn-masuk {
width: 100%;
padding: 14px;
background: var(--blue);
color: #fff;
border: none;
border-radius: 10px;
font-family: inherit;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: all .25s;
box-shadow: 0 4px 20px rgba(79, 124, 255, .35);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
}
.btn-masuk:hover {
background: #6b91ff;
transform: translateY(-1px);
box-shadow: 0 6px 28px rgba(79, 124, 255, .45);
}
.btn-masuk:active {
transform: translateY(0);
}
.btn-masuk.loading {
opacity: .7;
pointer-events: none;
}
/* Quick fill */
.quick-fill-label {
font-size: 11px;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .6px;
margin-bottom: 10px;
}
.quick-row {
display: flex;
gap: 8px;
}
.quick-btn {
flex: 1;
padding: 9px 8px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--sub);
font-family: inherit;
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all .2s;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
}
.quick-btn:hover {
background: rgba(255, 255, 255, .05);
border-color: rgba(255, 255, 255, .15);
color: var(--text);
}
.footer-note {
text-align: center;
margin-top: 28px;
font-size: 11px;
color: var(--muted);
}
/* Responsive */
@media (max-width: 768px) {
body {
grid-template-columns: 1fr;
}
.left-panel {
display: none;
}
.right-panel {
padding: 36px 24px;
}
}
</style>
</head>
<body>
<!-- LEFT: Map Visual Panel -->
<div class="left-panel">
<div class="map-bg"></div>
<div class="grid-lines"></div>
<!-- Animated map dots -->
<div class="dot dot-1"></div>
<div class="dot dot-2"></div>
<div class="dot dot-3"></div>
<div class="dot dot-4"></div>
<!-- SVG connecting lines -->
<div class="conn-lines">
<svg viewBox="0 0 100 100" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<line x1="28" y1="32" x2="55" y2="55" stroke="rgba(79,124,255,.2)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="55" y1="55" x2="60" y2="22" stroke="rgba(34,211,165,.15)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="28" y1="32" x2="35" y2="68" stroke="rgba(155,109,255,.15)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="35" y1="68" x2="55" y2="55" stroke="rgba(255,140,66,.12)" stroke-width=".3"
stroke-dasharray="2 2" />
</svg>
</div>
<div class="left-content">
<div class="left-tag">
<div class="left-tag-dot"></div>
Sistem Aktif
</div>
<h1 class="left-title">
Peta Kemiskinan<br>Berbasis <em>Rumah Ibadah</em>
</h1>
<p class="left-desc">
Platform pemetaan partisipatif yang menghubungkan data kemiskinan dengan jaringan rumah ibadah untuk
perencanaan program yang lebih tepat sasaran.
</p>
<div class="stat-row">
<div class="stat">
<span class="stat-val" style="color:var(--blue)">3</span>
<span class="stat-lbl">Jenis Peran</span>
</div>
<div class="stat-sep"></div>
<div class="stat">
<span class="stat-val" style="color:var(--green)">6+</span>
<span class="stat-lbl">Jenis Rumah Ibadah</span>
</div>
<div class="stat-sep"></div>
<div class="stat">
<span class="stat-val" style="color:var(--orange)">GIS</span>
<span class="stat-lbl">Berbasis Peta</span>
</div>
</div>
</div>
</div>
<!-- RIGHT: Login Panel -->
<div class="right-panel">
<div class="logo-row">
<div class="logo-icon">🗺️</div>
<div class="logo-text">
<span class="logo-name">WebGIS Poverty Mapping</span>
<span class="logo-sub">Sistem Informasi Geografis</span>
</div>
</div>
<h2 class="form-title">Masuk ke Sistem</h2>
<p class="form-sub">Pilih peran, lalu masukkan kredensial Anda.</p>
<!-- Role Tabs -->
<div class="role-tabs" id="role-tabs">
<button class="role-tab" id="tab-admin" data-role="admin" onclick="pilihRole('admin')">
<span class="role-tab-icon">🛡️</span>
<span class="role-tab-name">Admin</span>
</button>
<button class="role-tab" id="tab-pengurus" data-role="pengurus" onclick="pilihRole('pengurus')">
<span class="role-tab-icon">🕌</span>
<span class="role-tab-name">Pengurus</span>
</button>
<button class="role-tab" id="tab-walikota" data-role="walikota" onclick="pilihRole('walikota')">
<span class="role-tab-icon">👑</span>
<span class="role-tab-name">Walikota</span>
</button>
</div>
<!-- Error -->
<div class="error-msg" id="error-msg">
<span>⚠️</span>
<span id="error-text">Username atau password salah.</span>
</div>
<!-- Form -->
<form id="login-form" onsubmit="handleLogin(event)" novalidate>
<div class="field">
<label class="field-label" for="inp-username">Username</label>
<div class="field-wrap">
<span class="field-icon">👤</span>
<input class="field-input" type="text" id="inp-username" placeholder="Masukkan username"
autocomplete="username" required>
</div>
</div>
<div class="field">
<label class="field-label" for="inp-password">Password</label>
<div class="field-wrap">
<span class="field-icon">🔑</span>
<input class="field-input" type="password" id="inp-password" placeholder="Masukkan password"
autocomplete="current-password" required>
<button type="button" class="pw-toggle" id="pw-toggle" onclick="togglePw()">👁️</button>
</div>
</div>
<button type="submit" class="btn-masuk" id="btn-masuk">
<span id="btn-text">Masuk</span>
<span id="btn-arrow"></span>
</button>
</form>
<div style="margin-bottom: 28px;">
<a href="index.html" style="display:flex; justify-content:center; align-items:center; gap:8px; padding:14px; background:rgba(255,255,255,0.03); color:var(--text); border:1px solid var(--border); border-radius:10px; text-decoration:none; font-size:13.5px; font-weight:600; transition:all 0.25s;" onmouseover="this.style.background='rgba(255,255,255,0.08)'" onmouseout="this.style.background='rgba(255,255,255,0.03)'">
<span>🗺️ Lihat Peta & Lapor (Akses Warga)</span>
</a>
</div>
<!-- Quick Fill -->
<div class="quick-fill-label">Akun Demo — Klik untuk Isi Otomatis</div>
<div class="quick-row">
<button class="quick-btn" onclick="isiDemo('admin','admin123','admin')">🛡️ Admin</button>
<button class="quick-btn" onclick="isiDemo('pengurus','pengurus123','pengurus')">🕌 Pengurus</button>
<button class="quick-btn" onclick="isiDemo('walikota','walikota123','walikota')">👑 Walikota</button>
</div>
<div class="footer-note">WebGIS Poverty Mapping · 2026</div>
</div>
<script>
let roleSelected = null;
function pilihRole(role) {
roleSelected = role;
document.querySelectorAll('.role-tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + role).classList.add('active');
}
function isiDemo(user, pass, role) {
document.getElementById('inp-username').value = user;
document.getElementById('inp-password').value = pass;
pilihRole(role);
sembunyiError();
}
function togglePw() {
const inp = document.getElementById('inp-password');
const btn = document.getElementById('pw-toggle');
inp.type = inp.type === 'password' ? 'text' : 'password';
btn.textContent = inp.type === 'password' ? '👁️' : '🙈';
}
function tampilError(msg) {
const el = document.getElementById('error-msg');
document.getElementById('error-text').textContent = msg;
el.classList.add('show');
el.style.animation = 'none';
el.offsetHeight;
el.style.animation = '';
}
function sembunyiError() {
document.getElementById('error-msg').classList.remove('show');
}
async function handleLogin(e) {
e.preventDefault();
sembunyiError();
const username = document.getElementById('inp-username').value.trim();
const password = document.getElementById('inp-password').value;
if (!username) { tampilError('Username wajib diisi.'); return; }
if (!password) { tampilError('Password wajib diisi.'); return; }
if (!roleSelected) { tampilError('Pilih peran terlebih dahulu.'); return; }
const btn = document.getElementById('btn-masuk');
const btnText = document.getElementById('btn-text');
const btnArrow = document.getElementById('btn-arrow');
btn.classList.add('loading');
btnText.textContent = 'Memeriksa...';
btnArrow.textContent = '⏳';
try {
const res = await fetch('api_auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, role: roleSelected })
});
const data = await res.json();
if (data.success) {
sessionStorage.setItem('webgis_user', JSON.stringify({
id: data.user.id,
username: data.user.username,
nama: data.user.nama,
role: data.user.role
}));
btnText.textContent = 'Berhasil!';
btnArrow.textContent = '✓';
btn.style.background = '#22d3a5';
const target = data.user.role === 'walikota' ? 'dashboard_walikota.html' : 'index.html';
setTimeout(() => { window.location.href = target; }, 500);
} else {
tampilError(data.message || 'Username, password, atau peran tidak sesuai.');
btn.classList.remove('loading');
btnText.textContent = 'Masuk';
btnArrow.textContent = '→';
}
} catch {
tampilError('Gagal terhubung ke server. Pastikan Apache & MySQL aktif.');
btn.classList.remove('loading');
btnText.textContent = 'Masuk';
btnArrow.textContent = '→';
}
}
// Handle logout redirect
if (new URLSearchParams(location.search).get('logout')) {
sessionStorage.removeItem('webgis_user');
}
</script>
</body>
</html>
+101
View File
@@ -0,0 +1,101 @@
-- ============================================================
-- MIGRASI: Penyesuaian Proses Bisnis WebGIS v2.0
-- Jalankan di phpMyAdmin setelah database_lengkap.sql
-- ============================================================
USE webgis_p3;
-- ── 1. Tambah kolom NIK, No WA, dan alasan_tolak ke laporan_masyarakat ──
-- Periksa apakah kolom sudah ada sebelum menambahkan
-- Tambah kolom nik
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'webgis_p3' AND TABLE_NAME = 'laporan_masyarakat' AND COLUMN_NAME = 'nik');
SET @sql = IF(@col_exists = 0,
'ALTER TABLE laporan_masyarakat ADD COLUMN nik VARCHAR(20) DEFAULT NULL AFTER nama_pelapor',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Tambah kolom no_wa
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'webgis_p3' AND TABLE_NAME = 'laporan_masyarakat' AND COLUMN_NAME = 'no_wa');
SET @sql = IF(@col_exists = 0,
'ALTER TABLE laporan_masyarakat ADD COLUMN no_wa VARCHAR(20) DEFAULT NULL AFTER nik',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Tambah kolom alasan_tolak
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'webgis_p3' AND TABLE_NAME = 'laporan_masyarakat' AND COLUMN_NAME = 'alasan_tolak');
SET @sql = IF(@col_exists = 0,
'ALTER TABLE laporan_masyarakat ADD COLUMN alasan_tolak TEXT DEFAULT NULL AFTER status',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ── 2. Perluas ENUM status di laporan_masyarakat ──
-- Tambah status: Diverifikasi, Ditolak
ALTER TABLE laporan_masyarakat
MODIFY COLUMN status ENUM('Pending','Diproses','Diverifikasi','Ditolak','Selesai')
NOT NULL DEFAULT 'Pending';
-- ── 3. Pastikan tabel riwayat_bantuan ada ──
CREATE TABLE IF NOT EXISTS riwayat_bantuan (
id INT AUTO_INCREMENT PRIMARY KEY,
id_kemiskinan INT NOT NULL,
pemberi VARCHAR(200) NOT NULL,
jenis_bantuan VARCHAR(200) NOT NULL,
tanggal DATE NOT NULL,
keterangan TEXT DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (id_kemiskinan) REFERENCES data_kemiskinan(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── 4. Pastikan tabel laporan_masyarakat ada ──
CREATE TABLE IF NOT EXISTS laporan_masyarakat (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_pelapor VARCHAR(200) NOT NULL,
nik VARCHAR(20) DEFAULT NULL,
no_wa VARCHAR(20) DEFAULT NULL,
deskripsi TEXT NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
status ENUM('Pending','Diproses','Diverifikasi','Ditolak','Selesai') NOT NULL DEFAULT 'Pending',
alasan_tolak TEXT DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── 5. Tambahkan kolom yang mungkin belum ada di data_kemiskinan ──
-- status_bantuan
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'webgis_p3' AND TABLE_NAME = 'data_kemiskinan' AND COLUMN_NAME = 'status_bantuan');
SET @sql = IF(@col_exists = 0,
"ALTER TABLE data_kemiskinan ADD COLUMN status_bantuan VARCHAR(20) NOT NULL DEFAULT 'Belum' AFTER longitude",
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- kategori
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'webgis_p3' AND TABLE_NAME = 'data_kemiskinan' AND COLUMN_NAME = 'kategori');
SET @sql = IF(@col_exists = 0,
"ALTER TABLE data_kemiskinan ADD COLUMN kategori VARCHAR(50) NOT NULL DEFAULT 'Miskin' AFTER status_bantuan",
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ── 6. Update password walikota (gunakan generate_hash.php untuk update bcrypt) ──
-- Password walikota: walikota123
-- Jalankan generate_hash.php setelah migrasi ini untuk update hash
-- ============================================================
-- SELESAI. Jalankan generate_hash.php untuk update password hash.
-- URL: http://localhost/WebGIS/pertemuan fix/generate_hash.php
-- ============================================================
+28
View File
@@ -0,0 +1,28 @@
<?php
require_once 'config.php';
$db = getDB();
$queries = [
"ALTER TABLE laporan_masyarakat ADD COLUMN tanggal_lahir DATE NULL AFTER nama_pelapor",
"ALTER TABLE laporan_masyarakat ADD COLUMN pendidikan VARCHAR(50) NULL AFTER tanggal_lahir",
"ALTER TABLE laporan_masyarakat ADD COLUMN riwayat_penyakit VARCHAR(255) NULL AFTER pendidikan",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_penghasilan INT NULL AFTER riwayat_penyakit",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_rumah INT NULL AFTER skor_penghasilan",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_makan INT NULL AFTER skor_rumah",
"ALTER TABLE laporan_masyarakat ADD COLUMN jumlah_kk INT DEFAULT 1 AFTER skor_makan"
];
foreach ($queries as $q) {
try {
if ($db->query($q)) {
echo "Success: $q\n<br>";
} else {
echo "Error/Skipped (might already exist): " . $db->error . " | Query: $q\n<br>";
}
} catch (Exception $e) {
echo "Exception/Skipped: " . $e->getMessage() . " | Query: $q\n<br>";
}
}
$db->close();
echo "Migration complete.\n";
?>
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once 'config.php';
$db = getDB();
mysqli_report(MYSQLI_REPORT_OFF); // REALLY OFF
$queries = [
"ALTER TABLE laporan_masyarakat ADD COLUMN tanggal_lahir DATE NULL AFTER nama_pelapor",
"ALTER TABLE laporan_masyarakat ADD COLUMN pendidikan VARCHAR(50) NULL AFTER tanggal_lahir",
"ALTER TABLE laporan_masyarakat ADD COLUMN riwayat_penyakit VARCHAR(255) NULL AFTER pendidikan",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_penghasilan INT NULL AFTER riwayat_penyakit",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_rumah INT NULL AFTER skor_penghasilan",
"ALTER TABLE laporan_masyarakat ADD COLUMN skor_makan INT NULL AFTER skor_rumah",
"ALTER TABLE laporan_masyarakat ADD COLUMN jumlah_kk INT DEFAULT 1 AFTER skor_makan"
];
foreach ($queries as $q) {
try {
if (@$db->query($q)) {
echo "Success: $q\n<br>";
} else {
echo "Error/Skipped: " . $db->error . " | Query: $q\n<br>";
}
} catch (\Throwable $e) {
echo "Exception/Skipped: " . $e->getMessage() . " | Query: $q\n<br>";
}
}
$db->close();
echo "Migration complete.\n";
?>
+11
View File
@@ -0,0 +1,11 @@
<?php
// Test 1: localhost
$c1 = @new mysqli('localhost', 'root', '', 'webgis_p3');
echo "localhost: " . ($c1->connect_error ? "GAGAL - ".$c1->connect_error : "OK") . "<br>";
// Test 2: 127.0.0.1
$c2 = @new mysqli('127.0.0.1', 'root', '', 'webgis_p3');
echo "127.0.0.1: " . ($c2->connect_error ? "GAGAL - ".$c2->connect_error : "OK") . "<br>";
phpinfo();
?>
+89
View File
@@ -0,0 +1,89 @@
-- ============================================================
-- MIGRASI: Tabel Users & Role-Based Access Control
-- WebGIS Poverty Mapping Sistem Autentikasi
-- Jalankan di phpMyAdmin atau MySQL CLI setelah database.sql
-- ============================================================
USE webgis_p3;
-- ── Tabel pengguna ─────────────────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
nama_lengkap VARCHAR(200) NOT NULL,
role ENUM('admin','petugas','pengurus','walikota') NOT NULL,
password_hash VARCHAR(255) NOT NULL,
aktif TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_role (role),
INDEX idx_aktif (aktif)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ── Akun demo (password di-hash dengan bcrypt PHP PASSWORD_DEFAULT) ──
-- Gunakan script generate_hash.php untuk membuat hash baru jika perlu.
--
-- admin → admin123
-- petugas → petugas123
-- pengurus → pengurus123
--
-- Hash di bawah digenerate dengan PHP:
-- password_hash('admin123', PASSWORD_DEFAULT) → $2y$12$...
-- password_hash('petugas123', PASSWORD_DEFAULT) → $2y$12$...
-- password_hash('pengurus123', PASSWORD_DEFAULT) → $2y$12$...
--
-- !! Jangan gunakan plain text password di produksi !!
INSERT INTO users (username, nama_lengkap, role, password_hash) VALUES
(
'admin',
'Administrator Sistem',
'admin',
'$2y$12$YourHashHereReplaceMe.admin123HashGeneratedByPHP'
),
(
'petugas',
'Petugas Lapangan',
'petugas',
'$2y$12$YourHashHereReplaceMe.petugas123HashGeneratedByPHP'
),
(
'pengurus',
'Pengurus Rumah Ibadah',
'pengurus',
'$2y$12$YourHashHereReplaceMe.pengurus123HashGeneratedByPHP'
),
(
'walikota',
'Walikota Pontianak',
'walikota',
'$2y$10$s0387VqXY8wBXYkVytEKxumpF0DmlBlE1z3usinr0nr1RYN/4BZw.'
);
-- !! PENTING: Setelah import tabel di atas, jalankan generate_hash.php
-- untuk mengisi password_hash yang benar secara otomatis. !!
-- ── Catatan hak akses per role ──────────────────────────────
/*
ROLE: admin
─────────────────────────────────────────────────────────────
• CRUD semua data: SPBU, kemiskinan, rumah ibadah
• Dapat melihat semua layer & analisis spasial
• Fitur FAB Tambah: SPBU, Kemiskinan, Rumah Ibadah
ROLE: petugas
─────────────────────────────────────────────────────────────
• Input & edit data kemiskinan (CREATE + UPDATE + DELETE)
• View semua data (read-only untuk SPBU & Rumah Ibadah)
• Fitur FAB Tambah: hanya Kemiskinan
• Tidak dapat hapus data SPBU atau Rumah Ibadah
• Tidak dapat tambah/edit SPBU atau Rumah Ibadah
ROLE: pengurus
─────────────────────────────────────────────────────────────
• Input & edit data rumah ibadah miliknya (CREATE + UPDATE + DELETE)
• View data kemiskinan (read-only)
• Fitur FAB Tambah: hanya Rumah Ibadah
• Tidak dapat hapus data SPBU atau kemiskinan
• Tidak dapat tambah/edit SPBU atau kemiskinan
*/