Initial commit - clean and ready for Coolify
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
$pdo = new PDO('mysql:host=localhost;dbname=webgis_baru;charset=utf8', 'root', '');
|
||||
$pdo->exec("ALTER TABLE donasi_digital MODIFY metode_pembayaran VARCHAR(50) NOT NULL");
|
||||
echo 'Table altered successfully';
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// KONEKSI DATABASE MENGGUNAKAN STANDAR PDO
|
||||
$host = 'localhost';
|
||||
$dbname = 'webgis_baru'; // Sesuaikan jika nama databasemu berbeda
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
die(json_encode(["error" => "Koneksi PDO Gagal: " . $e->getMessage()]));
|
||||
}
|
||||
|
||||
$aksi = $_GET['aksi'] ?? 'global';
|
||||
|
||||
if ($aksi == 'global') {
|
||||
// 1. PERBAIKAN: Menambahkan WHERE status = 'berhasil' agar donasi pending tidak ikut terhitung di index.php
|
||||
$stmtMasuk = $pdo->query("SELECT SUM(nominal) AS total_masuk, COUNT(id) AS jml_donatur FROM donasi_digital WHERE status = 'berhasil'");
|
||||
$dataMasuk = $stmtMasuk->fetch(PDO::FETCH_ASSOC);
|
||||
$total_donasi = $dataMasuk['total_masuk'] ?? 0;
|
||||
$jml_donatur = $dataMasuk['jml_donatur'] ?? 0;
|
||||
|
||||
// 2. Tarik Semua Log Distribusi ke Warga Miskin
|
||||
$stmtDistribusi = $pdo->query("
|
||||
SELECT
|
||||
h.id, h.jenis_id, h.tanggal_distribusi, h.nama_penyalur, h.jumlah, h.satuan_custom, h.catatan,
|
||||
w.nama_kk, w.lat, w.lng
|
||||
FROM histori_bantuan h
|
||||
JOIN warga_miskin w ON h.warga_id = w.id
|
||||
ORDER BY h.tanggal_distribusi DESC
|
||||
");
|
||||
$log_distribusi = $stmtDistribusi->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
"total_donasi" => $total_donasi,
|
||||
"jumlah_donatur" => $jml_donatur,
|
||||
"log_distribusi" => $log_distribusi
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
ob_start(); // ← ob_start PERTAMA, sebelum apapun
|
||||
|
||||
session_start();
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
header('Content-Type: application/json'); // ← header setelah ob_start aman
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
ob_end_clean(); // ← sekarang ini valid karena ob_start sudah jalan
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// KONEKSI DATABASE STANDAR PDO
|
||||
$host = 'localhost';
|
||||
$dbname = 'webgis_baru';
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
die(json_encode(["error" => "Koneksi Gagal: " . $e->getMessage()]));
|
||||
}
|
||||
|
||||
// POST request dari form donasi tidak membawa ?aksi=, default ke 'simpan'
|
||||
$aksi = $_GET['aksi'] ?? ($_SERVER['REQUEST_METHOD'] === 'POST' ? 'simpan' : 'tampil');
|
||||
|
||||
// 0. CREATE: SIMPAN DONASI BARU DARI WARGA
|
||||
if ($aksi == 'simpan') {
|
||||
$metode = $_POST['metode'] ?? '';
|
||||
$nominal = intval($_POST['nominal'] ?? 0);
|
||||
$nama = trim($_POST['nama'] ?? 'Anonim');
|
||||
$pesan = trim($_POST['pesan'] ?? '');
|
||||
$ibadahId = intval($_POST['ibadah_id'] ?? 0);
|
||||
$jenisDonasi = trim($_POST['jenis_donasi'] ?? 'Uang Tunai');
|
||||
$deskripsiBarang = trim($_POST['deskripsi_barang'] ?? '');
|
||||
|
||||
// Validasi minimal
|
||||
if (empty($metode)) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Metode pembayaran wajib diisi."]);
|
||||
exit;
|
||||
}
|
||||
if ($jenisDonasi === 'Uang Tunai') {
|
||||
if ($nominal < 10000) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Nominal minimal Rp 10.000."]);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Donasi barang — metode tidak wajib, deskripsi wajib
|
||||
if (empty($deskripsiBarang)) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Deskripsi barang wajib diisi."]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Upload bukti (opsional untuk donasi barang)
|
||||
$buktiPath = '';
|
||||
if (isset($_FILES['bukti']) && $_FILES['bukti']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = 'uploads/bukti_donasi/';
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
|
||||
|
||||
$ext = strtolower(pathinfo($_FILES['bukti']['name'], PATHINFO_EXTENSION));
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'pdf'];
|
||||
|
||||
if (!in_array($ext, $allowed)) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Format file tidak didukung."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$namaFile = 'donasi_' . time() . '_' . uniqid() . '.' . $ext;
|
||||
move_uploaded_file($_FILES['bukti']['tmp_name'], $uploadDir . $namaFile);
|
||||
$buktiPath = $uploadDir . $namaFile;
|
||||
}
|
||||
|
||||
// Untuk donasi barang, simpan deskripsi di kolom pesan_dukungan
|
||||
$pesanFinal = $jenisDonasi !== 'Uang Tunai'
|
||||
? "[BARANG] $deskripsiBarang" . ($pesan ? " | Pesan: $pesan" : '')
|
||||
: $pesan;
|
||||
|
||||
// Ibadah ID 0 artinya umum, simpan sebagai NULL
|
||||
$ibadahIdFinal = $ibadahId > 0 ? $ibadahId : null;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO donasi_digital
|
||||
(metode_pembayaran, nominal, nama_donatur, pesan_dukungan, ibadah_id, bukti_transfer, status, created_at, jenis_donasi, deskripsi_barang)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'pending', NOW(), ?, ?)
|
||||
");
|
||||
|
||||
if ($stmt->execute([$metode, $nominal, $nama, $pesanFinal, $ibadahIdFinal, $buktiPath, $jenisDonasi, $deskripsiBarang])) {
|
||||
echo json_encode(["status" => "sukses", "pesan" => "Donasi berhasil dikirim."]);
|
||||
} else {
|
||||
echo json_encode(["status" => "error", "pesan" => "Gagal menyimpan ke database."]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. READ: TAMPILKAN LOG DONASI KE DASHBOARD
|
||||
if ($aksi == 'tampil') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT d.*, r.nama_tempat AS disalurkan_ke
|
||||
FROM donasi_digital d
|
||||
LEFT JOIN rumah_ibadah r ON d.ibadah_id = r.id
|
||||
ORDER BY d.id DESC
|
||||
");
|
||||
$donasi = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// TRANSFORMAST DATA: Menerjemahkan bahasa Database ke bahasa UI Dashboard
|
||||
foreach ($donasi as &$d) {
|
||||
$status_db = strtolower($d['status'] ?? '');
|
||||
|
||||
if ($status_db == 'pending') {
|
||||
$d['status'] = 'Menunggu';
|
||||
} elseif ($status_db == 'berhasil') {
|
||||
$d['status'] = 'Dikonfirmasi';
|
||||
} elseif ($status_db == 'ditolak' || $status_db == 'gagal') {
|
||||
$d['status'] = 'Ditolak';
|
||||
}
|
||||
|
||||
// Gunakan jenis_donasi dari database (bisa 'Uang Tunai', 'Sembako', 'Pakaian', dll.)
|
||||
$jenis = !empty($d['jenis_donasi']) ? $d['jenis_donasi'] : 'Uang Tunai';
|
||||
$d['jenis_donasi'] = $jenis;
|
||||
|
||||
// Untuk donasi uang: tampilkan nominal + metode pembayaran
|
||||
// Untuk donasi barang: tampilkan deskripsi barang
|
||||
if ($jenis === 'Uang Tunai') {
|
||||
$d['nilai'] = number_format((float)$d['nominal'], 0, ',', '.');
|
||||
$d['keterangan'] = $d['pesan_dukungan'] . ($d['metode_pembayaran'] ? " (" . $d['metode_pembayaran'] . ")" : "");
|
||||
} else {
|
||||
// Donasi barang: nilai diisi deskripsi barang, bukan nominal
|
||||
$deskripsi = !empty($d['deskripsi_barang']) ? $d['deskripsi_barang'] : '';
|
||||
$d['nilai'] = $deskripsi ?: ($d['pesan_dukungan'] ?: '—');
|
||||
$d['keterangan'] = $d['pesan_dukungan'];
|
||||
}
|
||||
|
||||
$d['tanggal'] = date('Y-m-d', strtotime($d['created_at']));
|
||||
$d['kontak'] = "Bukti: " . $d['bukti_transfer'];
|
||||
|
||||
if (empty($d['disalurkan_ke'])) {
|
||||
$d['disalurkan_ke'] = 'Umum';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($donasi);
|
||||
exit;
|
||||
|
||||
// 2. UPDATE: KONFIRMASI / TOLAK DONASI
|
||||
} elseif ($aksi == 'update_status') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$status_ui = $_POST['status'] ?? 'Menunggu';
|
||||
|
||||
// PENERJEMAH KE DATABASE: Mengubah bahasa UI kembali ke bahasa Database
|
||||
$status_db = 'pending';
|
||||
if ($status_ui == 'Dikonfirmasi') {
|
||||
$status_db = 'berhasil';
|
||||
} elseif ($status_ui == 'Ditolak') {
|
||||
$status_db = 'gagal'; // enum DB: 'gagal', bukan 'ditolak'
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE donasi_digital SET status = ? WHERE id = ?");
|
||||
if ($stmt->execute([$status_db, $id])) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal memperbarui status";
|
||||
}
|
||||
exit;
|
||||
|
||||
// 3. DELETE: HAPUS LOG DONASI
|
||||
} elseif ($aksi == 'hapus') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$stmt = $pdo->prepare("DELETE FROM donasi_digital WHERE id = ?");
|
||||
if ($stmt->execute([$id])) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal menghapus data";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
include 'koneksi.php';
|
||||
$aksi = $_GET['aksi'];
|
||||
|
||||
include 'koneksi.php';
|
||||
$aksi = $_GET['aksi'];
|
||||
|
||||
// 🛡️ PROTEKSI KEAMANAN GLOBAL UNTUK WARGA
|
||||
$role_aktif = $_SESSION['role'] ?? 'warga';
|
||||
|
||||
// Jika aksi adalah sesuatu yang merubah data (simpan, update, hapus)
|
||||
if (in_array($aksi, ['simpan', 'update', 'hapus', 'update_lokasi', 'simpan_bantuan', 'update_bantuan', 'hapus_bantuan'])) {
|
||||
|
||||
// Blokir total jika role-nya adalah warga atau walikota
|
||||
if ($role_aktif === 'warga' || $role_aktif === 'walikota') {
|
||||
echo "Gagal: Akses ditolak. Warga hanya diizinkan untuk melihat data dan melakukan donasi.";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aksi == 'tampil') {
|
||||
$result = $conn->query("SELECT * FROM rumah_ibadah");
|
||||
$data = array();
|
||||
while($row = $result->fetch_assoc()) { $data[] = $row; }
|
||||
echo json_encode($data);
|
||||
|
||||
} elseif ($aksi == 'simpan') {
|
||||
$nama = $conn->real_escape_string($_POST['nama_tempat'] ?? '');
|
||||
// Tangkap kategori dari formulir
|
||||
$kategori = $conn->real_escape_string($_POST['kategori'] ?? 'Masjid');
|
||||
$pic = $conn->real_escape_string($_POST['pic'] ?? '');
|
||||
$alamat = $conn->real_escape_string($_POST['alamat'] ?? '');
|
||||
$radius = intval($_POST['radius'] ?? 500);
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
|
||||
if ($nama === '' || $lat === 0 || $lng === 0) {
|
||||
echo "Gagal: Nama tempat dan koordinat tidak boleh kosong.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Masukkan kategori ke dalam query INSERT
|
||||
$sql = "INSERT INTO rumah_ibadah (nama_tempat, kategori, pic, alamat, radius, lat, lng)
|
||||
VALUES ('$nama', '$kategori', '$pic', '$alamat', $radius, $lat, $lng)";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
|
||||
} elseif ($aksi == 'hapus') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
if($conn->query("DELETE FROM rumah_ibadah WHERE id=$id")) echo "Sukses"; else echo "Gagal";
|
||||
|
||||
} elseif ($aksi == 'update') {
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
$session_masjid = $_SESSION['nama_masjid'] ?? '';
|
||||
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$nama_tempat = $conn->real_escape_string($_POST['nama_tempat'] ?? '');
|
||||
// Tangkap kategori saat proses edit
|
||||
$kategori = $conn->real_escape_string($_POST['kategori'] ?? 'Masjid');
|
||||
$pic = $conn->real_escape_string($_POST['pic'] ?? '');
|
||||
$alamat = $conn->real_escape_string($_POST['alamat'] ?? '');
|
||||
$radius = intval($_POST['radius'] ?? 500);
|
||||
|
||||
if ($role === 'pengurus_masjid') {
|
||||
$cek = $conn->query("SELECT nama_tempat FROM rumah_ibadah WHERE id = $id")->fetch_assoc();
|
||||
if (!$cek || $cek['nama_tempat'] !== $session_masjid) {
|
||||
echo "Gagal: Anda tidak memiliki wewenang mengedit data masjid lain.";
|
||||
exit;
|
||||
}
|
||||
} elseif ($role !== 'admin') {
|
||||
echo "Gagal: Akses ditolak.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Masukkan kategori ke dalam query UPDATE
|
||||
$sql = "UPDATE rumah_ibadah SET nama_tempat='$nama_tempat', kategori='$kategori', pic='$pic', alamat='$alamat', radius=$radius WHERE id=$id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'update_lokasi') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
if($conn->query("UPDATE rumah_ibadah SET lat=$lat, lng=$lng WHERE id=$id")) echo "Sukses"; else echo "Gagal";
|
||||
}
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
mysqli_report(MYSQLI_REPORT_OFF);
|
||||
include 'koneksi.php';
|
||||
|
||||
include 'koneksi.php';
|
||||
$aksi = $_GET['aksi'];
|
||||
|
||||
// 🛡️ PROTEKSI KEAMANAN GLOBAL UNTUK WARGA
|
||||
$role_aktif = $_SESSION['role'] ?? 'warga';
|
||||
|
||||
// Jika aksi adalah sesuatu yang merubah data (simpan, update, hapus)
|
||||
if (in_array($aksi, ['simpan', 'update', 'hapus', 'update_lokasi', 'simpan_bantuan', 'update_bantuan', 'hapus_bantuan'])) {
|
||||
|
||||
// Blokir total jika role-nya adalah warga atau walikota
|
||||
if ($role_aktif === 'warga' || $role_aktif === 'walikota') {
|
||||
echo "Gagal: Akses ditolak. Warga hanya diizinkan untuk melihat data dan melakukan donasi.";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi hitung jarak radius di sisi Server (Backend Security)
|
||||
function hitungJarak($lat1, $lon1, $lat2, $lon2) {
|
||||
$earth_radius = 6371000; // Radius bumi dalam meter
|
||||
$dLat = deg2rad($lat2 - $lat1);
|
||||
$dLon = deg2rad($lon2 - $lon1);
|
||||
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2);
|
||||
$c = 2 * asin(sqrt($a));
|
||||
return $earth_radius * $c;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$nama_tabel_bantuan = 'histori_bantuan';
|
||||
$aksi = $_GET['aksi'] ?? '';
|
||||
|
||||
// Ambil Sesi Global
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
$nama_masjid = $_SESSION['nama_masjid'] ?? '';
|
||||
$user_id = $_SESSION['user_id'] ?? 0;
|
||||
|
||||
if ($aksi == 'tampil') {
|
||||
$query = $conn->query("SELECT * FROM warga_miskin");
|
||||
|
||||
if (!$query) {
|
||||
echo json_encode(["error" => "Gagal memuat data warga_miskin: " . $conn->error]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
while ($row = $query->fetch_assoc()) {
|
||||
$id_warga = $row['id'];
|
||||
|
||||
// 1. Ambil data anggota keluarga
|
||||
$query_anggota = $conn->query("SELECT * FROM anggota_keluarga WHERE id_warga = '$id_warga'");
|
||||
$anggota = [];
|
||||
if ($query_anggota) {
|
||||
while ($row_anggota = $query_anggota->fetch_assoc()) {
|
||||
$anggota[] = $row_anggota;
|
||||
}
|
||||
}
|
||||
$row['anggota'] = $anggota;
|
||||
|
||||
// 2. Ambil bantuan TERAKHIR
|
||||
$query_bantuan = $conn->query("
|
||||
SELECT * FROM $nama_tabel_bantuan
|
||||
WHERE warga_id = '$id_warga'
|
||||
ORDER BY tanggal_distribusi DESC
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
if ($query_bantuan && $query_bantuan->num_rows > 0) {
|
||||
$bantuan_terakhir = $query_bantuan->fetch_assoc();
|
||||
|
||||
$tgl_bantuan = new DateTime($bantuan_terakhir['tanggal_distribusi']);
|
||||
$tgl_sekarang = new DateTime();
|
||||
$interval = $tgl_sekarang->diff($tgl_bantuan);
|
||||
|
||||
$selisih_bulan = ($interval->y * 12) + $interval->m;
|
||||
$sumber = $bantuan_terakhir['nama_penyalur'] ?: 'Rumah Ibadah';
|
||||
|
||||
if ($selisih_bulan == 0) {
|
||||
$row['status_bantuan'] = "Warga ini baru saja menerima bantuan bulan ini dari " . $sumber;
|
||||
} else {
|
||||
$row['status_bantuan'] = "Warga ini terakhir menerima bantuan " . $selisih_bulan . " bulan lalu dari " . $sumber;
|
||||
}
|
||||
} else {
|
||||
$row['status_bantuan'] = "Warga ini belum pernah menerima bantuan.";
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'tampil_bantuan') {
|
||||
$query = $conn->query("
|
||||
SELECT
|
||||
h.id, h.warga_id, h.tanggal_distribusi AS tanggal, h.nama_penyalur AS sumber_bantuan,
|
||||
IF(h.catatan IS NOT NULL AND h.catatan != '', h.catatan, 'Bantuan Sosial') AS jenis_bantuan,
|
||||
w.nama_kk, w.lat, w.lng
|
||||
FROM $nama_tabel_bantuan h
|
||||
JOIN warga_miskin w ON h.warga_id = w.id
|
||||
ORDER BY h.tanggal_distribusi DESC
|
||||
");
|
||||
|
||||
$bantuan = [];
|
||||
if ($query) {
|
||||
while ($row = $query->fetch_assoc()) { $bantuan[] = $row; }
|
||||
}
|
||||
echo json_encode($bantuan);
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'simpan') {
|
||||
$nama = $conn->real_escape_string($_POST['nama_kk'] ?? '');
|
||||
$jml = intval($_POST['jumlah_anggota'] ?? 0);
|
||||
$alamat = $conn->real_escape_string($_POST['alamat'] ?? '');
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
|
||||
$pekerjaan = $conn->real_escape_string($_POST['pekerjaan'] ?? '');
|
||||
$penyakit = $conn->real_escape_string($_POST['riwayat_penyakit'] ?? '');
|
||||
|
||||
$tgl_lahir = !empty($_POST['tanggal_lahir']) ? "'" . $conn->real_escape_string($_POST['tanggal_lahir']) . "'" : "NULL";
|
||||
$pendidikan = !empty($_POST['pendidikan_terakhir']) ? "'" . $conn->real_escape_string($_POST['pendidikan_terakhir']) . "'" : "NULL";
|
||||
$jaminan = !empty($_POST['kepemilikan_jaminan']) ? "'" . $conn->real_escape_string($_POST['kepemilikan_jaminan']) . "'" : "NULL";
|
||||
|
||||
$fakir = isset($_POST['is_fakir_miskin']) ? intval($_POST['is_fakir_miskin']) : 0;
|
||||
$dhuafa = isset($_POST['is_dhuafa']) ? intval($_POST['is_dhuafa']) : 0;
|
||||
$lansia = isset($_POST['is_lansia_tunggal']) ? intval($_POST['is_lansia_tunggal']) : 0;
|
||||
$yatim = isset($_POST['is_anak_yatim']) ? intval($_POST['is_anak_yatim']) : 0;
|
||||
$bencana = isset($_POST['is_korban_bencana']) ? intval($_POST['is_korban_bencana']) : 0;
|
||||
$disab = isset($_POST['is_disabilitas']) ? intval($_POST['is_disabilitas']) : 0;
|
||||
|
||||
if (trim($nama) === '' || $jml <= 0 || trim($alamat) === '' || !isset($_POST['lat']) || !isset($_POST['lng'])) {
|
||||
echo json_encode(["status" => "Gagal", "pesan" => "Data utama tidak lengkap"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// PERBAIKAN: Menambahkan diinput_oleh_id agar tombol Edit/Hapus bisa muncul di akun pengurus
|
||||
$sql = "INSERT INTO warga_miskin
|
||||
(nama_kk, jumlah_anggota, alamat, lat, lng, tanggal_lahir, pendidikan_terakhir, pekerjaan, riwayat_penyakit, kepemilikan_jaminan,
|
||||
is_fakir_miskin, is_dhuafa, is_lansia_tunggal, is_anak_yatim, is_korban_bencana, is_disabilitas, diinput_oleh_id)
|
||||
VALUES
|
||||
('$nama', $jml, '$alamat', $lat, $lng, $tgl_lahir, $pendidikan, '$pekerjaan', '$penyakit', $jaminan,
|
||||
$fakir, $dhuafa, $lansia, $yatim, $bencana, $disab, $user_id)";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
$id_warga = $conn->insert_id;
|
||||
$anggota_json = $_POST['anggota'] ?? '[]';
|
||||
$anggota_list = json_decode($anggota_json, true);
|
||||
|
||||
if (!empty($anggota_list) && is_array($anggota_list)) {
|
||||
foreach ($anggota_list as $an) {
|
||||
$an_nama = $conn->real_escape_string($an['nama'] ?? '');
|
||||
$an_hub = $conn->real_escape_string($an['hubungan_kk'] ?? '');
|
||||
$an_jk = !empty($an['jenis_kelamin']) ? "'" . $conn->real_escape_string($an['jenis_kelamin']) . "'" : 'NULL';
|
||||
$an_tgl = !empty($an['tanggal_lahir']) ? "'" . $conn->real_escape_string($an['tanggal_lahir']) . "'" : 'NULL';
|
||||
$an_pend = !empty($an['pendidikan_terakhir']) ? "'" . $conn->real_escape_string($an['pendidikan_terakhir']) . "'" : 'NULL';
|
||||
$an_kerja = $conn->real_escape_string($an['pekerjaan'] ?? '');
|
||||
$an_sakit = $conn->real_escape_string($an['riwayat_penyakit'] ?? '');
|
||||
$an_jamin = !empty($an['kepemilikan_jaminan']) ? "'" . $conn->real_escape_string($an['kepemilikan_jaminan']) . "'" : 'NULL';
|
||||
|
||||
$conn->query("
|
||||
INSERT INTO anggota_keluarga
|
||||
(id_warga, nama, tanggal_lahir, jenis_kelamin, hubungan_kk, pendidikan_terakhir, pekerjaan, riwayat_penyakit, kepemilikan_jaminan)
|
||||
VALUES
|
||||
($id_warga, '$an_nama', $an_tgl, $an_jk, '$an_hub', $an_pend, '$an_kerja', '$an_sakit', $an_jamin)
|
||||
");
|
||||
}
|
||||
}
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'hapus') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
|
||||
// PROTEKSI GEO-FENCING BACKEND
|
||||
if ($role === 'pengurus_masjid') {
|
||||
$wargaRes = $conn->query("SELECT lat, lng FROM warga_miskin WHERE id = $id");
|
||||
$masjidRes = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE nama_tempat = '$nama_masjid'");
|
||||
|
||||
if ($wargaRes && $masjidRes && $wargaRes->num_rows > 0 && $masjidRes->num_rows > 0) {
|
||||
$warga = $wargaRes->fetch_assoc();
|
||||
$masjid = $masjidRes->fetch_assoc();
|
||||
|
||||
$jarak = hitungJarak($warga['lat'], $warga['lng'], $masjid['lat'], $masjid['lng']);
|
||||
if ($jarak > $masjid['radius']) {
|
||||
echo "Gagal: Warga berada di luar radius wewenang masjid Anda.";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} elseif ($role !== 'admin') {
|
||||
echo "Gagal: Akses ditolak.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// PERBAIKAN: Perintah Hapus Utama Ditambahkan
|
||||
$conn->query("DELETE FROM anggota_keluarga WHERE id_warga = $id"); // Hapus data anak tabel dulu
|
||||
if ($conn->query("DELETE FROM warga_miskin WHERE id = $id")) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'update') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
|
||||
// PROTEKSI GEO-FENCING BACKEND
|
||||
if ($role === 'pengurus_masjid') {
|
||||
$wargaRes = $conn->query("SELECT lat, lng FROM warga_miskin WHERE id = $id");
|
||||
$masjidRes = $conn->query("SELECT lat, lng, radius FROM rumah_ibadah WHERE nama_tempat = '$nama_masjid'");
|
||||
|
||||
if ($wargaRes && $masjidRes && $wargaRes->num_rows > 0 && $masjidRes->num_rows > 0) {
|
||||
$warga = $wargaRes->fetch_assoc();
|
||||
$masjid = $masjidRes->fetch_assoc();
|
||||
|
||||
$jarak = hitungJarak($warga['lat'], $warga['lng'], $masjid['lat'], $masjid['lng']);
|
||||
if ($jarak > $masjid['radius']) {
|
||||
echo "Gagal: Warga berada di luar radius wewenang masjid Anda.";
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo "Gagal: Data gagal diverifikasi.";
|
||||
exit;
|
||||
}
|
||||
} elseif ($role !== 'admin') {
|
||||
echo "Gagal: Akses ditolak.";
|
||||
exit;
|
||||
}
|
||||
|
||||
// PERBAIKAN: Menangkap Semua Post Data dan Mengeksekusi Update
|
||||
$nama = $conn->real_escape_string($_POST['nama_kk'] ?? '');
|
||||
$jml = intval($_POST['jumlah_anggota'] ?? 0);
|
||||
$alamat = $conn->real_escape_string($_POST['alamat'] ?? '');
|
||||
$pekerjaan = $conn->real_escape_string($_POST['pekerjaan'] ?? '');
|
||||
$penyakit = $conn->real_escape_string($_POST['riwayat_penyakit'] ?? '');
|
||||
$tgl_lahir = !empty($_POST['tanggal_lahir']) ? "'" . $conn->real_escape_string($_POST['tanggal_lahir']) . "'" : "NULL";
|
||||
$pendidikan = !empty($_POST['pendidikan_terakhir']) ? "'" . $conn->real_escape_string($_POST['pendidikan_terakhir']) . "'" : "NULL";
|
||||
$jaminan = !empty($_POST['kepemilikan_jaminan']) ? "'" . $conn->real_escape_string($_POST['kepemilikan_jaminan']) . "'" : "NULL";
|
||||
|
||||
$fakir = isset($_POST['is_fakir_miskin']) ? intval($_POST['is_fakir_miskin']) : 0;
|
||||
$dhuafa = isset($_POST['is_dhuafa']) ? intval($_POST['is_dhuafa']) : 0;
|
||||
$lansia = isset($_POST['is_lansia_tunggal']) ? intval($_POST['is_lansia_tunggal']) : 0;
|
||||
$yatim = isset($_POST['is_anak_yatim']) ? intval($_POST['is_anak_yatim']) : 0;
|
||||
$bencana = isset($_POST['is_korban_bencana']) ? intval($_POST['is_korban_bencana']) : 0;
|
||||
$disab = isset($_POST['is_disabilitas']) ? intval($_POST['is_disabilitas']) : 0;
|
||||
|
||||
$sql = "UPDATE warga_miskin SET
|
||||
nama_kk = '$nama', jumlah_anggota = $jml, alamat = '$alamat',
|
||||
tanggal_lahir = $tgl_lahir, pendidikan_terakhir = $pendidikan, pekerjaan = '$pekerjaan',
|
||||
riwayat_penyakit = '$penyakit', kepemilikan_jaminan = $jaminan,
|
||||
is_fakir_miskin = $fakir, is_dhuafa = $dhuafa, is_lansia_tunggal = $lansia,
|
||||
is_anak_yatim = $yatim, is_korban_bencana = $bencana, is_disabilitas = $disab
|
||||
WHERE id = $id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'update_lokasi') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
|
||||
if ($conn->query("UPDATE warga_miskin SET lat=$lat, lng=$lng WHERE id=$id")) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'simpan_bantuan') {
|
||||
$warga_id = intval($_POST['warga_id'] ?? 0);
|
||||
$jenis_id = intval($_POST['jenis_id'] ?? 0);
|
||||
$tgl = $conn->real_escape_string($_POST['tanggal_distribusi'] ?? '');
|
||||
$penyalur = $conn->real_escape_string($_POST['nama_penyalur'] ?? '');
|
||||
$deskripsi= $conn->real_escape_string($_POST['deskripsi_bantuan'] ?? '');
|
||||
$catatan = $conn->real_escape_string($_POST['catatan'] ?? '');
|
||||
|
||||
if ($warga_id <= 0 || $jenis_id <= 0 || $tgl == '' || $penyalur == '' || $deskripsi == '') {
|
||||
echo "Gagal: Data tidak lengkap";
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO $nama_tabel_bantuan
|
||||
(warga_id, jenis_id, tanggal_distribusi, nama_penyalur, jumlah, satuan_custom, catatan)
|
||||
VALUES
|
||||
($warga_id, $jenis_id, '$tgl', '$penyalur', 1, '$deskripsi', '$catatan')";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'update_bantuan') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$jenis_id = intval($_POST['jenis_id'] ?? 0);
|
||||
$tgl = $conn->real_escape_string($_POST['tanggal_distribusi'] ?? '');
|
||||
$penyalur = $conn->real_escape_string($_POST['nama_penyalur'] ?? '');
|
||||
$deskripsi= $conn->real_escape_string($_POST['deskripsi_bantuan'] ?? '');
|
||||
$catatan = $conn->real_escape_string($_POST['catatan'] ?? '');
|
||||
|
||||
if ($id <= 0 || $jenis_id <= 0 || $tgl == '' || $penyalur == '' || $deskripsi == '') {
|
||||
echo "Gagal: Data tidak lengkap";
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE $nama_tabel_bantuan SET
|
||||
jenis_id = $jenis_id,
|
||||
tanggal_distribusi = '$tgl',
|
||||
nama_penyalur = '$penyalur',
|
||||
satuan_custom = '$deskripsi',
|
||||
catatan = '$catatan'
|
||||
WHERE id = $id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
|
||||
} elseif ($aksi == 'hapus_bantuan') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo "Gagal: ID tidak valid";
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM $nama_tabel_bantuan WHERE id = $id";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
echo "Sukses";
|
||||
} else {
|
||||
echo "Gagal: " . $conn->error;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
// =============================================
|
||||
// api_laporan.php
|
||||
// Menerima laporan publik dari lapor.php
|
||||
// Status awal: "menunggu" (belum tampil di peta)
|
||||
// =============================================
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// ——— KONEKSI DATABASE ———
|
||||
// Sesuaikan dengan konfigurasi Laragon kamu
|
||||
$host = 'localhost';
|
||||
$user = 'root';
|
||||
$pass = ''; // Laragon default: kosong
|
||||
$db = 'webgis_baru';
|
||||
|
||||
$conn = new mysqli($host, $user, $pass, $db);
|
||||
if ($conn->connect_error) {
|
||||
echo json_encode(['status' => 'error', 'pesan' => 'Koneksi database gagal']);
|
||||
exit;
|
||||
}
|
||||
$conn->set_charset('utf8mb4');
|
||||
|
||||
// ——— PASTIKAN TABEL LAPORAN ADA ———
|
||||
// Tabel ini terpisah dari warga_miskin.
|
||||
// Data masuk dulu ke sini, operator verifikasi,
|
||||
// baru dipindahkan ke warga_miskin.
|
||||
$conn->query("
|
||||
CREATE TABLE IF NOT EXISTS laporan_publik (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
lat DECIMAL(10,7) NOT NULL,
|
||||
lng DECIMAL(10,7) NOT NULL,
|
||||
nama_kk VARCHAR(255) NOT NULL,
|
||||
jumlah_anggota INT NOT NULL,
|
||||
alamat TEXT NOT NULL,
|
||||
tanggal_lahir DATE NULL,
|
||||
pendidikan_terakhir ENUM('Tidak Sekolah','SD','SMP','SMA','D3','S1','S2','S3') NULL,
|
||||
pekerjaan VARCHAR(100) NULL,
|
||||
riwayat_penyakit TEXT NULL,
|
||||
kepemilikan_jaminan ENUM('BPJS Kesehatan','KIS','BPJS Ketenagakerjaan','Tidak Ada') NULL,
|
||||
nama_pelapor VARCHAR(255) NOT NULL,
|
||||
hp_pelapor VARCHAR(20) NULL,
|
||||
hubungan_pelapor VARCHAR(50) NULL,
|
||||
keterangan TEXT NULL,
|
||||
status ENUM('menunggu','disetujui','ditolak') DEFAULT 'menunggu',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
");
|
||||
|
||||
// ——— VALIDASI INPUT ———
|
||||
$lat = floatval($_POST['lat'] ?? 0);
|
||||
$lng = floatval($_POST['lng'] ?? 0);
|
||||
$nama_kk = trim($conn->real_escape_string($_POST['nama_kk'] ?? ''));
|
||||
$jumlah_anggota = intval($_POST['jumlah_anggota'] ?? 0);
|
||||
$alamat = trim($conn->real_escape_string($_POST['alamat'] ?? ''));
|
||||
$nama_pelapor = trim($conn->real_escape_string($_POST['nama_pelapor'] ?? ''));
|
||||
|
||||
if (!$lat || !$lng || !$nama_kk || $jumlah_anggota < 1 || !$alamat || !$nama_pelapor) {
|
||||
echo json_encode(['status' => 'error', 'pesan' => 'Data wajib tidak lengkap']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ——— DATA OPSIONAL ———
|
||||
$tgl_lahir = !empty($_POST['tanggal_lahir']) ? "'" . $conn->real_escape_string($_POST['tanggal_lahir']) . "'" : 'NULL';
|
||||
$pendidikan = !empty($_POST['pendidikan_terakhir']) ? "'" . $conn->real_escape_string($_POST['pendidikan_terakhir']) . "'" : 'NULL';
|
||||
$pekerjaan = trim($conn->real_escape_string($_POST['pekerjaan'] ?? ''));
|
||||
$penyakit = trim($conn->real_escape_string($_POST['riwayat_penyakit'] ?? ''));
|
||||
$jaminan = !empty($_POST['kepemilikan_jaminan']) ? "'" . $conn->real_escape_string($_POST['kepemilikan_jaminan']) . "'" : 'NULL';
|
||||
$hp = trim($conn->real_escape_string($_POST['hp_pelapor'] ?? ''));
|
||||
$hubungan = trim($conn->real_escape_string($_POST['hubungan_pelapor'] ?? ''));
|
||||
$keterangan = trim($conn->real_escape_string($_POST['keterangan'] ?? ''));
|
||||
|
||||
// ——— INSERT ———
|
||||
$sql = "
|
||||
INSERT INTO laporan_publik
|
||||
(lat, lng, nama_kk, jumlah_anggota, alamat,
|
||||
tanggal_lahir, pendidikan_terakhir, pekerjaan,
|
||||
riwayat_penyakit, kepemilikan_jaminan,
|
||||
nama_pelapor, hp_pelapor, hubungan_pelapor, keterangan, status)
|
||||
VALUES
|
||||
($lat, $lng, '$nama_kk', $jumlah_anggota, '$alamat',
|
||||
$tgl_lahir, $pendidikan, '$pekerjaan',
|
||||
'$penyakit', $jaminan,
|
||||
'$nama_pelapor', '$hp', '$hubungan', '$keterangan', 'menunggu')
|
||||
";
|
||||
|
||||
if ($conn->query($sql)) {
|
||||
$newId = $conn->insert_id;
|
||||
echo json_encode(['status' => 'ok', 'id' => $newId]);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'pesan' => 'Gagal menyimpan: ' . $conn->error]);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
ob_start();
|
||||
session_start();
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak. Silakan login."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$host = 'localhost';
|
||||
$dbname = 'webgis_baru';
|
||||
$dbuser = 'root';
|
||||
$dbpass = '';
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $dbuser, $dbpass);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Koneksi DB gagal."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$aksi = $_GET['aksi'] ?? '';
|
||||
$user_id = (int)($_SESSION['user_id'] ?? 0);
|
||||
$user_role = $_SESSION['role'] ?? 'warga';
|
||||
|
||||
// ─── 1. SIMPAN LAPORAN (warga) ───────────────────────────────────────────────
|
||||
if ($aksi === 'simpan') {
|
||||
$tipe = in_array($_POST['tipe_laporan'] ?? '', ['diri_sendiri','orang_lain']) ? $_POST['tipe_laporan'] : 'orang_lain';
|
||||
$nama = trim($_POST['nama_kk'] ?? '');
|
||||
$jml = max(1, (int)($_POST['jumlah_anggota'] ?? 1));
|
||||
$alamat = trim($_POST['alamat'] ?? '');
|
||||
$lat = (float)($_POST['lat'] ?? 0);
|
||||
$lng = (float)($_POST['lng'] ?? 0);
|
||||
$pekerjaan = trim($_POST['pekerjaan'] ?? '');
|
||||
$ket = trim($_POST['keterangan'] ?? '');
|
||||
|
||||
$fakir = (int)(($_POST['is_fakir_miskin'] ?? 0) == '1');
|
||||
$dhuafa = (int)(($_POST['is_dhuafa'] ?? 0) == '1');
|
||||
$lansia = (int)(($_POST['is_lansia_tunggal'] ?? 0) == '1');
|
||||
$yatim = (int)(($_POST['is_anak_yatim'] ?? 0) == '1');
|
||||
$bencana = (int)(($_POST['is_korban_bencana'] ?? 0) == '1');
|
||||
$disab = (int)(($_POST['is_disabilitas'] ?? 0) == '1');
|
||||
|
||||
if (empty($nama) || empty($alamat) || $lat == 0 || $lng == 0) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Nama, alamat, dan titik lokasi di peta wajib diisi."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO laporan_warga
|
||||
(pelapor_id, tipe_laporan, nama_kk, jumlah_anggota, alamat, lat, lng,
|
||||
pekerjaan, keterangan,
|
||||
is_fakir_miskin, is_dhuafa, is_lansia_tunggal, is_anak_yatim, is_korban_bencana, is_disabilitas)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
");
|
||||
$stmt->execute([
|
||||
$user_id, $tipe, $nama, $jml, $alamat, $lat, $lng,
|
||||
$pekerjaan, $ket,
|
||||
$fakir, $dhuafa, $lansia, $yatim, $bencana, $disab
|
||||
]);
|
||||
$id = $pdo->lastInsertId();
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "sukses", "id" => $id, "pesan" => "Laporan #$id berhasil dikirim. Menunggu verifikasi admin."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── 2. TAMPIL SEMUA LAPORAN (admin/walikota) ────────────────────────────────
|
||||
if ($aksi === 'tampil') {
|
||||
if (!in_array($user_role, ['admin', 'walikota'])) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Akses ditolak."]);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->query("
|
||||
SELECT l.*, u.username AS nama_pelapor
|
||||
FROM laporan_warga l
|
||||
LEFT JOIN users u ON l.pelapor_id = u.id
|
||||
ORDER BY l.created_at DESC
|
||||
");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
ob_end_clean();
|
||||
echo json_encode($rows);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── 3. CEK STATUS LAPORAN MILIK SENDIRI (warga) ────────────────────────────
|
||||
if ($aksi === 'cek_status') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, tipe_laporan, nama_kk, alamat, status, catatan_admin, created_at
|
||||
FROM laporan_warga
|
||||
WHERE pelapor_id = ?
|
||||
ORDER BY created_at DESC
|
||||
");
|
||||
$stmt->execute([$user_id]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
ob_end_clean();
|
||||
echo json_encode($rows);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── 4. VERIFIKASI → masuk ke warga_miskin (admin) ──────────────────────────
|
||||
if ($aksi === 'verifikasi') {
|
||||
if ($user_role !== 'admin') {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Hanya admin yang dapat memverifikasi."]);
|
||||
exit;
|
||||
}
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "ID laporan tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ambil data laporan
|
||||
$stmt = $pdo->prepare("SELECT * FROM laporan_warga WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$lap = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$lap) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Laporan tidak ditemukan."]);
|
||||
exit;
|
||||
}
|
||||
if ($lap['status'] === 'diverifikasi') {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Laporan sudah pernah diverifikasi."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert ke warga_miskin
|
||||
$ins = $pdo->prepare("
|
||||
INSERT INTO warga_miskin
|
||||
(nama_kk, jumlah_anggota, alamat, lat, lng, pekerjaan,
|
||||
is_fakir_miskin, is_dhuafa, is_lansia_tunggal, is_anak_yatim, is_korban_bencana, is_disabilitas,
|
||||
diinput_oleh_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
");
|
||||
$ins->execute([
|
||||
$lap['nama_kk'], $lap['jumlah_anggota'], $lap['alamat'],
|
||||
$lap['lat'], $lap['lng'], $lap['pekerjaan'],
|
||||
$lap['is_fakir_miskin'], $lap['is_dhuafa'], $lap['is_lansia_tunggal'],
|
||||
$lap['is_anak_yatim'], $lap['is_korban_bencana'], $lap['is_disabilitas'],
|
||||
$lap['pelapor_id']
|
||||
]);
|
||||
$warga_id = $pdo->lastInsertId();
|
||||
|
||||
// Update status laporan
|
||||
$upd = $pdo->prepare("UPDATE laporan_warga SET status='diverifikasi', warga_id=? WHERE id=?");
|
||||
$upd->execute([$warga_id, $id]);
|
||||
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "sukses", "warga_id" => $warga_id, "pesan" => "Laporan diverifikasi. Data warga masuk ke peta."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── 5. TOLAK LAPORAN (admin) ────────────────────────────────────────────────
|
||||
if ($aksi === 'tolak') {
|
||||
if ($user_role !== 'admin') {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Hanya admin yang dapat menolak laporan."]);
|
||||
exit;
|
||||
}
|
||||
$id = (int)($_POST['id'] ?? 0);
|
||||
$catatan = trim($_POST['catatan_admin'] ?? '');
|
||||
if ($id <= 0) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "ID tidak valid."]);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("UPDATE laporan_warga SET status='ditolak', catatan_admin=? WHERE id=?");
|
||||
$stmt->execute([$catatan, $id]);
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "sukses", "pesan" => "Laporan ditolak."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ─── 6. HITUNG LAPORAN MENUNGGU (admin, untuk badge) ────────────────────────
|
||||
if ($aksi === 'hitung_menunggu') {
|
||||
if (!in_array($user_role, ['admin', 'walikota'])) {
|
||||
ob_end_clean();
|
||||
echo json_encode(["jumlah" => 0]);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM laporan_warga WHERE status='menunggu'");
|
||||
$n = (int)$stmt->fetchColumn();
|
||||
ob_end_clean();
|
||||
echo json_encode(["jumlah" => $n]);
|
||||
exit;
|
||||
}
|
||||
|
||||
ob_end_clean();
|
||||
echo json_encode(["status" => "error", "pesan" => "Aksi tidak dikenal: $aksi"]);
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
session_start();
|
||||
include 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// Validasi input kosong
|
||||
if (empty($username) || empty($password)) {
|
||||
header("Location: login.php?error=empty");
|
||||
exit;
|
||||
}
|
||||
|
||||
$username_esc = $conn->real_escape_string($username);
|
||||
$sql = "SELECT * FROM users WHERE username = '$username_esc'";
|
||||
$result = $conn->query($sql);
|
||||
|
||||
if ($result->num_rows > 0) {
|
||||
$user = $result->fetch_assoc();
|
||||
|
||||
if (password_verify($password, $user['password'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
$_SESSION['nama_masjid'] = $user['nama_masjid'] ?? '';
|
||||
|
||||
// Arahkan ke dashboard jika admin/walikota, ke peta jika warga
|
||||
if (in_array($user['role'], ['admin', 'walikota'])) {
|
||||
header("Location: dashboard.php");
|
||||
} else {
|
||||
header("Location: index.php");
|
||||
}
|
||||
exit;
|
||||
} else {
|
||||
header("Location: login.php?error=invalid");
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
header("Location: login.php?error=invalid");
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
$pdo = new PDO('mysql:host=localhost;dbname=webgis_baru;charset=utf8', 'root', '');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS laporan_warga (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
pelapor_id INT NOT NULL,
|
||||
tipe_laporan ENUM('diri_sendiri','orang_lain') DEFAULT 'orang_lain',
|
||||
nama_kk VARCHAR(150) NOT NULL,
|
||||
jumlah_anggota INT DEFAULT 1,
|
||||
alamat TEXT NOT NULL,
|
||||
lat DECIMAL(10,7) DEFAULT 0,
|
||||
lng DECIMAL(10,7) DEFAULT 0,
|
||||
pekerjaan VARCHAR(100) DEFAULT '',
|
||||
keterangan TEXT,
|
||||
is_fakir_miskin TINYINT(1) DEFAULT 0,
|
||||
is_dhuafa TINYINT(1) DEFAULT 0,
|
||||
is_lansia_tunggal TINYINT(1) DEFAULT 0,
|
||||
is_anak_yatim TINYINT(1) DEFAULT 0,
|
||||
is_korban_bencana TINYINT(1) DEFAULT 0,
|
||||
is_disabilitas TINYINT(1) DEFAULT 0,
|
||||
status ENUM('menunggu','diverifikasi','ditolak') DEFAULT 'menunggu',
|
||||
catatan_admin TEXT,
|
||||
warga_id INT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
|
||||
$pdo->exec($sql);
|
||||
echo "OK: Tabel laporan_warga berhasil dibuat!\n";
|
||||
echo "Kolom: " . implode(', ', array_column($pdo->query('DESCRIBE laporan_warga')->fetchAll(PDO::FETCH_ASSOC), 'Field')) . "\n";
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
echo password_hash("1234567", PASSWORD_DEFAULT);
|
||||
?>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
$conn = new mysqli("localhost", "root", "", "webgis_baru");
|
||||
if ($conn->connect_error) {
|
||||
die("Koneksi gagal: " . $conn->connect_error);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,704 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Lapor Warga — WebGIS Pemetaan Sosial</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--blue: #30a8e0;
|
||||
--blue-d: #1b8ec7;
|
||||
--blue-lt: #ddf0fb;
|
||||
--navy: #0b2545;
|
||||
--green: #1fad63;
|
||||
--green-d: #168a4f;
|
||||
--green-lt: #d7f5e7;
|
||||
--red: #e05050;
|
||||
--orange: #f59e0b;
|
||||
--glass: rgba(255,255,255,0.94);
|
||||
--shadow: 0 8px 32px rgba(11,37,69,0.18);
|
||||
--shadow-sm: 0 2px 12px rgba(11,37,69,0.12);
|
||||
--r: 14px;
|
||||
--r2: 10px;
|
||||
--r3: 8px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { min-height: 100%; font-family: 'Plus Jakarta Sans', sans-serif; background: #f0f4f8; color: var(--navy); }
|
||||
|
||||
/* LAYOUT */
|
||||
.page-wrap {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 420px;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.page-header {
|
||||
grid-column: 1 / -1;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(14px);
|
||||
border-bottom: 1px solid rgba(11,37,69,0.09);
|
||||
padding: 14px 24px;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
box-shadow: var(--shadow-sm);
|
||||
position: sticky; top: 0; z-index: 100;
|
||||
}
|
||||
.header-brand { display: flex; align-items: center; gap: 10px; }
|
||||
.hb-icon {
|
||||
width: 36px; height: 36px;
|
||||
background: linear-gradient(135deg, var(--green), var(--blue));
|
||||
border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 17px; box-shadow: 0 3px 10px rgba(31,173,99,0.3);
|
||||
}
|
||||
.hb-text .title { font-size: 14px; font-weight: 800; color: var(--navy); line-height: 1.2; }
|
||||
.hb-text .subtitle{ font-size: 10px; color: rgba(11,37,69,0.42); font-weight: 500; }
|
||||
.header-badge {
|
||||
background: var(--orange); color: white;
|
||||
font-size: 10px; font-weight: 700;
|
||||
padding: 4px 11px; border-radius: 20px;
|
||||
}
|
||||
.back-link {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
font-size: 12px; font-weight: 600;
|
||||
color: rgba(11,37,69,0.45);
|
||||
text-decoration: none;
|
||||
padding: 6px 12px; border-radius: var(--r3);
|
||||
border: 1.5px solid rgba(11,37,69,0.1);
|
||||
background: rgba(11,37,69,0.03);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.back-link:hover { background: rgba(11,37,69,0.07); color: var(--navy); }
|
||||
|
||||
/* PETA */
|
||||
.map-panel { position: relative; background: #e8eff5; }
|
||||
#map-lapor { width: 100%; height: 100%; min-height: 500px; cursor: crosshair; }
|
||||
|
||||
.map-instruction {
|
||||
position: absolute; bottom: 20px; left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 500;
|
||||
background: rgba(11,37,69,0.82); color: rgba(255,255,255,0.9);
|
||||
font-size: 12px; font-weight: 500;
|
||||
padding: 8px 18px; border-radius: 20px;
|
||||
backdrop-filter: blur(8px); pointer-events: none; white-space: nowrap;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.map-instruction.picked { background: rgba(31,173,99,0.88); }
|
||||
|
||||
.coord-pill {
|
||||
position: absolute; top: 12px; left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 500;
|
||||
background: var(--glass); backdrop-filter: blur(12px);
|
||||
border-radius: 20px; padding: 6px 14px;
|
||||
font-size: 11px; font-weight: 600; color: var(--navy);
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: none; align-items: center; gap: 6px;
|
||||
}
|
||||
.coord-pill.show { display: flex; }
|
||||
|
||||
/* FORM PANEL */
|
||||
.form-panel {
|
||||
background: white;
|
||||
border-left: 1px solid rgba(11,37,69,0.08);
|
||||
display: flex; flex-direction: column;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 64px);
|
||||
position: sticky; top: 64px;
|
||||
}
|
||||
.form-panel::-webkit-scrollbar { width: 4px; }
|
||||
.form-panel::-webkit-scrollbar-thumb { background: rgba(11,37,69,0.14); border-radius: 4px; }
|
||||
|
||||
/* HERO FORM */
|
||||
.form-hero {
|
||||
background: linear-gradient(135deg, #0b2545 0%, #1a3f6f 100%);
|
||||
padding: 22px 22px 18px;
|
||||
position: relative; overflow: hidden;
|
||||
}
|
||||
.form-hero::before {
|
||||
content: ''; position: absolute;
|
||||
top: -20px; right: -20px; width: 100px; height: 100px;
|
||||
background: rgba(48,168,224,0.15); border-radius: 50%;
|
||||
}
|
||||
.form-hero::after {
|
||||
content: ''; position: absolute;
|
||||
bottom: -30px; left: 30px; width: 80px; height: 80px;
|
||||
background: rgba(31,173,99,0.1); border-radius: 50%;
|
||||
}
|
||||
.fh-label { font-size: 10px; font-weight: 700; color: rgba(255,255,255,0.45); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 5px; }
|
||||
.fh-title { font-size: 20px; font-weight: 800; color: white; line-height: 1.2; margin-bottom: 6px; }
|
||||
.fh-desc { font-size: 12px; color: rgba(255,255,255,0.55); line-height: 1.6; }
|
||||
|
||||
/* STEPS */
|
||||
.steps-bar {
|
||||
display: flex; padding: 14px 22px;
|
||||
border-bottom: 1px solid rgba(11,37,69,0.08);
|
||||
background: rgba(11,37,69,0.02);
|
||||
}
|
||||
.step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; position: relative; }
|
||||
.step:not(:last-child)::after {
|
||||
content: ''; position: absolute;
|
||||
top: 12px; left: 60%; width: 80%; height: 2px;
|
||||
background: rgba(11,37,69,0.1); z-index: 0;
|
||||
}
|
||||
.step.done:not(:last-child)::after { background: var(--green); }
|
||||
.step-num {
|
||||
width: 24px; height: 24px; border-radius: 50%;
|
||||
background: rgba(11,37,69,0.08); border: 2px solid rgba(11,37,69,0.12);
|
||||
font-size: 11px; font-weight: 700; color: rgba(11,37,69,0.35);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
position: relative; z-index: 1; transition: all 0.25s;
|
||||
}
|
||||
.step.active .step-num { background: var(--blue-lt); border-color: var(--blue); color: var(--blue-d); }
|
||||
.step.done .step-num { background: var(--green-lt); border-color: var(--green); color: var(--green-d); }
|
||||
.step-lbl { font-size: 9px; font-weight: 600; color: rgba(11,37,69,0.35); text-align: center; }
|
||||
.step.active .step-lbl { color: var(--blue-d); }
|
||||
.step.done .step-lbl { color: var(--green-d); }
|
||||
|
||||
/* FORM BODY */
|
||||
.form-body { padding: 18px 22px; flex: 1; }
|
||||
.form-section { margin-bottom: 18px; }
|
||||
.section-title {
|
||||
font-size: 11px; font-weight: 700;
|
||||
color: rgba(11,37,69,0.45);
|
||||
text-transform: uppercase; letter-spacing: 0.08em;
|
||||
margin-bottom: 10px;
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.section-title::after { content: ''; flex: 1; height: 1px; background: rgba(11,37,69,0.08); }
|
||||
|
||||
.ff { margin-bottom: 11px; }
|
||||
.ff label {
|
||||
display: block; font-size: 10px; font-weight: 700;
|
||||
color: rgba(11,37,69,0.42); text-transform: uppercase;
|
||||
letter-spacing: 0.07em; margin-bottom: 4px;
|
||||
}
|
||||
.ff label .req { color: var(--red); margin-left: 2px; }
|
||||
.ff input, .ff select, .ff textarea {
|
||||
width: 100%; padding: 9px 11px; font-size: 13px; font-family: inherit;
|
||||
border: 1.5px solid rgba(11,37,69,0.12); border-radius: var(--r3);
|
||||
color: var(--navy); background: rgba(11,37,69,0.02);
|
||||
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
|
||||
appearance: none; -webkit-appearance: none;
|
||||
}
|
||||
.ff select {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%230b2545' stroke-opacity='0.35' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat; background-position: right 11px center; padding-right: 32px; cursor: pointer;
|
||||
}
|
||||
.ff textarea { resize: vertical; min-height: 72px; line-height: 1.55; }
|
||||
.ff input:focus, .ff select:focus, .ff textarea:focus {
|
||||
outline: none; border-color: var(--blue);
|
||||
box-shadow: 0 0 0 3px rgba(48,168,224,0.11); background: white;
|
||||
}
|
||||
.ff input::placeholder, .ff textarea::placeholder { color: rgba(11,37,69,0.26); }
|
||||
.ff-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
|
||||
/* LOKASI BOX */
|
||||
.loc-box {
|
||||
background: rgba(11,37,69,0.03);
|
||||
border: 1.5px dashed rgba(11,37,69,0.15);
|
||||
border-radius: var(--r2); padding: 12px 14px;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
margin-bottom: 11px; transition: all 0.2s; cursor: pointer;
|
||||
}
|
||||
.loc-box.filled { background: var(--green-lt); border-color: var(--green); border-style: solid; }
|
||||
.loc-box-icon {
|
||||
width: 36px; height: 36px; border-radius: 9px;
|
||||
background: rgba(11,37,69,0.06);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 16px; flex-shrink: 0; transition: background 0.2s;
|
||||
}
|
||||
.loc-box.filled .loc-box-icon { background: rgba(31,173,99,0.15); }
|
||||
.loc-box-text .lbt-title { font-size: 12px; font-weight: 700; color: rgba(11,37,69,0.5); }
|
||||
.loc-box.filled .loc-box-text .lbt-title { color: var(--green-d); }
|
||||
.loc-box-text .lbt-val { font-size: 11px; color: rgba(11,37,69,0.38); margin-top: 2px; font-family: monospace; }
|
||||
.loc-box.filled .loc-box-text .lbt-val { color: var(--green-d); font-weight: 600; }
|
||||
|
||||
/* INFO BOX */
|
||||
.info-box {
|
||||
background: rgba(245,158,11,0.08);
|
||||
border: 1px solid rgba(245,158,11,0.25);
|
||||
border-radius: var(--r2); padding: 10px 12px;
|
||||
font-size: 11px; color: rgba(11,37,69,0.6);
|
||||
line-height: 1.6; margin-bottom: 14px;
|
||||
display: flex; gap: 8px;
|
||||
}
|
||||
.info-box .ib-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
|
||||
|
||||
/* FORM FOOTER */
|
||||
.form-footer {
|
||||
padding: 14px 22px 20px;
|
||||
border-top: 1px solid rgba(11,37,69,0.08);
|
||||
background: rgba(11,37,69,0.01);
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%; padding: 13px; border: none; border-radius: var(--r2);
|
||||
background: linear-gradient(135deg, var(--green), var(--blue));
|
||||
color: white; font-family: inherit; font-size: 14px; font-weight: 700;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
display: flex; align-items: center; justify-content: center; gap: 7px;
|
||||
box-shadow: 0 4px 15px rgba(31,173,99,0.3);
|
||||
}
|
||||
.btn-submit:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(31,173,99,0.4); }
|
||||
.btn-submit:active { transform: scale(0.98); }
|
||||
.btn-submit:disabled {
|
||||
background: rgba(11,37,69,0.12); color: rgba(11,37,69,0.35);
|
||||
cursor: not-allowed; box-shadow: none; transform: none;
|
||||
}
|
||||
.submit-note { text-align: center; font-size: 10px; color: rgba(11,37,69,0.3); margin-top: 8px; }
|
||||
|
||||
/* SUCCESS OVERLAY */
|
||||
#success-overlay {
|
||||
display: none; position: fixed; inset: 0; z-index: 9000;
|
||||
background: rgba(11,37,69,0.5); backdrop-filter: blur(6px);
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
#success-overlay.show { display: flex; }
|
||||
.success-card {
|
||||
background: white; border-radius: 20px; padding: 36px 32px;
|
||||
max-width: 360px; width: calc(100vw - 32px);
|
||||
text-align: center; box-shadow: 0 30px 80px rgba(11,37,69,0.3);
|
||||
animation: scaleIn 0.35s cubic-bezier(0.34,1.5,0.64,1);
|
||||
}
|
||||
@keyframes scaleIn { from { opacity:0; transform:scale(0.75); } to { opacity:1; transform:scale(1); } }
|
||||
.sc-circle {
|
||||
width: 72px; height: 72px; background: var(--green-lt); border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 32px; margin: 0 auto 16px;
|
||||
}
|
||||
.sc-title { font-size: 20px; font-weight: 800; color: var(--navy); margin-bottom: 8px; }
|
||||
.sc-desc { font-size: 13px; color: rgba(11,37,69,0.5); line-height: 1.6; margin-bottom: 20px; }
|
||||
.sc-id { background: var(--green-lt); border-radius: var(--r2); padding: 10px 14px; font-size: 12px; font-weight: 700; color: var(--green-d); margin-bottom: 20px; }
|
||||
.btn-lapor-lagi {
|
||||
width: 100%; padding: 11px; border: none; border-radius: var(--r2);
|
||||
background: var(--green); color: white; font-family: inherit;
|
||||
font-size: 13px; font-weight: 700; cursor: pointer; transition: background 0.15s;
|
||||
}
|
||||
.btn-lapor-lagi:hover { background: var(--green-d); }
|
||||
|
||||
/* TOAST — identik dengan index.php */
|
||||
#toast {
|
||||
position: fixed; bottom: 24px; left: 50%;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
z-index: 9999;
|
||||
background: var(--navy); color: white;
|
||||
font-size: 12px; font-weight: 600;
|
||||
padding: 8px 20px; border-radius: 18px;
|
||||
box-shadow: 0 6px 22px rgba(11,37,69,0.28);
|
||||
opacity: 0; transition: all 0.28s ease; pointer-events: none;
|
||||
font-family: 'Plus Jakarta Sans', sans-serif; white-space: nowrap;
|
||||
}
|
||||
#toast.show { opacity:1; transform: translateX(-50%) translateY(0); }
|
||||
#toast.ok { background: var(--green); }
|
||||
#toast.err { background: var(--red); }
|
||||
|
||||
/* RESPONSIVE MOBILE */
|
||||
@media (max-width: 768px) {
|
||||
.page-wrap { grid-template-columns: 1fr; grid-template-rows: auto auto auto; }
|
||||
.map-panel { grid-row: 2; height: 280px; }
|
||||
.form-panel { grid-row: 3; max-height: none; position: static; overflow-y: visible; }
|
||||
#map-lapor { min-height: 280px; }
|
||||
.ff-row { grid-template-columns: 1fr; }
|
||||
.back-link span { display: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="
|
||||
margin-top:16px; padding-top:14px;
|
||||
border-top:1px dashed rgba(11,37,69,0.1);
|
||||
text-align:center">
|
||||
<div style="font-size:10px;font-weight:700;color:rgba(11,37,69,0.35);
|
||||
text-transform:uppercase;letter-spacing:0.07em;margin-bottom:8px">
|
||||
Bagikan halaman ini
|
||||
</div>
|
||||
<div id="self-qr" style="width:100px;height:100px;border-radius:8px;border:1.5px solid rgba(11,37,69,0.08);overflow:hidden"></div>
|
||||
<div style="font-size:10px;color:rgba(11,37,69,0.3);margin-top:6px">
|
||||
Scan untuk membuka di HP lain
|
||||
</div>
|
||||
</div>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<!-- HEADER -->
|
||||
<header class="page-header">
|
||||
<div class="header-brand">
|
||||
<div class="hb-icon">🗺️</div>
|
||||
<div class="hb-text">
|
||||
<div class="title">WebGIS Pemetaan Sosial</div>
|
||||
<div class="subtitle">Kota Pontianak — Berbasis Komunitas Rumah Ibadah</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="header-badge">📢 Laporan Publik</div>
|
||||
<a href="index.php" class="back-link">← <span>Kembali ke Peta</span></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- PANEL KIRI: PETA -->
|
||||
<div class="map-panel">
|
||||
<div id="map-lapor"></div>
|
||||
<div class="coord-pill" id="coordPill">
|
||||
📍 <span id="coordText">—</span>
|
||||
</div>
|
||||
<div class="map-instruction" id="mapHint">
|
||||
Klik titik lokasi tempat tinggal di peta
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PANEL KANAN: FORM -->
|
||||
<div class="form-panel">
|
||||
|
||||
<!-- Hero -->
|
||||
<div class="form-hero">
|
||||
<div class="fh-label">Formulir Laporan Warga</div>
|
||||
<div class="fh-title">Laporkan Keluarga<br>Tidak Mampu</div>
|
||||
<div class="fh-desc">Data Anda akan diverifikasi oleh pengurus rumah ibadah setempat sebelum masuk ke peta resmi.</div>
|
||||
</div>
|
||||
|
||||
<!-- Steps -->
|
||||
<div class="steps-bar">
|
||||
<div class="step done" id="step1"><div class="step-num">1</div><div class="step-lbl">Mulai</div></div>
|
||||
<div class="step active" id="step2"><div class="step-num">2</div><div class="step-lbl">Lokasi</div></div>
|
||||
<div class="step" id="step3"><div class="step-num">3</div><div class="step-lbl">Data Diri</div></div>
|
||||
<div class="step" id="step4"><div class="step-num">4</div><div class="step-lbl">Kirim</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="form-body">
|
||||
|
||||
<div class="info-box">
|
||||
<span class="ib-icon">💡</span>
|
||||
<span>Klik di peta sebelah kiri untuk menentukan lokasi, lalu isi data di bawah ini.</span>
|
||||
</div>
|
||||
|
||||
<!-- LOKASI -->
|
||||
<div class="section-title">📍 Lokasi Tempat Tinggal</div>
|
||||
<div class="loc-box" id="locBox" onclick="focusMap()">
|
||||
<div class="loc-box-icon">📍</div>
|
||||
<div class="loc-box-text">
|
||||
<div class="lbt-title" id="locTitle">Belum dipilih — klik peta</div>
|
||||
<div class="lbt-val" id="locVal">Klik di peta untuk menentukan koordinat</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Alamat Lengkap <span class="req">*</span></label>
|
||||
<textarea id="f_alamat" placeholder="Contoh: Jl. Ahmad Yani No. 12, Kel. Sungai Bangkong, Kec. Pontianak Kota"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- DATA PRIBADI -->
|
||||
<div class="section-title">👤 Data Kepala Keluarga</div>
|
||||
<div class="ff">
|
||||
<label>Nama Kepala Keluarga <span class="req">*</span></label>
|
||||
<input type="text" id="f_kk" placeholder="Nama lengkap sesuai KTP" />
|
||||
</div>
|
||||
<div class="ff-row">
|
||||
<div class="ff">
|
||||
<label>Jumlah Anggota (Jiwa) <span class="req">*</span></label>
|
||||
<input type="number" id="f_jml" placeholder="Contoh: 4" min="1" max="30" />
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Tanggal Lahir KK</label>
|
||||
<input type="date" id="f_tgl_lahir" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ff-row">
|
||||
<div class="ff">
|
||||
<label>Pendidikan Terakhir</label>
|
||||
<select id="f_pendidikan">
|
||||
<option value="">— Pilih —</option>
|
||||
<option value="Tidak Sekolah">Tidak Sekolah</option>
|
||||
<option value="SD">SD</option>
|
||||
<option value="SMP">SMP</option>
|
||||
<option value="SMA">SMA</option>
|
||||
<option value="D3">D3</option>
|
||||
<option value="S1">S1</option>
|
||||
<option value="S2">S2</option>
|
||||
<option value="S3">S3</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Pekerjaan</label>
|
||||
<input type="text" id="f_pekerjaan" placeholder="Contoh: Buruh harian" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KESEHATAN -->
|
||||
<div class="section-title">🏥 Kesehatan & Jaminan Sosial</div>
|
||||
<div class="ff">
|
||||
<label>Riwayat Penyakit / Disabilitas</label>
|
||||
<textarea id="f_penyakit" placeholder="Contoh: Diabetes, hipertensi — atau tulis 'Tidak ada'" style="min-height:56px"></textarea>
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Kepemilikan Jaminan Sosial</label>
|
||||
<select id="f_jaminan">
|
||||
<option value="">— Pilih —</option>
|
||||
<option value="BPJS Kesehatan">BPJS Kesehatan</option>
|
||||
<option value="KIS">KIS (Kartu Indonesia Sehat)</option>
|
||||
<option value="BPJS Ketenagakerjaan">BPJS Ketenagakerjaan</option>
|
||||
<option value="Tidak Ada">Tidak Ada</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- PELAPOR -->
|
||||
<div class="section-title">📝 Identitas Pelapor</div>
|
||||
<div class="ff">
|
||||
<label>Nama Pelapor <span class="req">*</span></label>
|
||||
<input type="text" id="f_pelapor" placeholder="Nama Anda (pengurus / relawan / warga)" />
|
||||
</div>
|
||||
<div class="ff-row">
|
||||
<div class="ff">
|
||||
<label>No. HP Pelapor</label>
|
||||
<input type="tel" id="f_hp" placeholder="08xx-xxxx-xxxx" />
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Hubungan dengan KK</label>
|
||||
<select id="f_hubungan">
|
||||
<option value="">— Pilih —</option>
|
||||
<option value="Diri sendiri">Diri sendiri</option>
|
||||
<option value="Tetangga">Tetangga</option>
|
||||
<option value="Pengurus RT/RW">Pengurus RT/RW</option>
|
||||
<option value="Relawan">Relawan</option>
|
||||
<option value="Pengurus Rumah Ibadah">Pengurus Rumah Ibadah</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ff">
|
||||
<label>Keterangan Tambahan</label>
|
||||
<textarea id="f_keterangan" placeholder="Ceritakan kondisi keluarga secara singkat (opsional)"></textarea>
|
||||
</div>
|
||||
|
||||
</div><!-- /form-body -->
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="form-footer">
|
||||
<button class="btn-submit" id="btnSubmit" onclick="submitLaporan()" disabled>
|
||||
📤 Kirim Laporan untuk Diverifikasi
|
||||
</button>
|
||||
<div class="submit-note">
|
||||
⚠️ Laporan akan masuk sebagai <strong>menunggu verifikasi</strong> — belum tampil di peta sebelum disetujui operator
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /form-panel -->
|
||||
</div><!-- /page-wrap -->
|
||||
|
||||
<!-- SUCCESS OVERLAY -->
|
||||
<div id="success-overlay">
|
||||
<div class="success-card">
|
||||
<div class="sc-circle">✅</div>
|
||||
<div class="sc-title">Laporan Terkirim!</div>
|
||||
<div class="sc-desc">Terima kasih. Laporan Anda sudah diterima dan akan segera diverifikasi oleh pengurus rumah ibadah setempat.</div>
|
||||
<div class="sc-id" id="sc-ref">ID Laporan: #—</div>
|
||||
<button class="btn-lapor-lagi" onclick="resetForm()">📋 Laporkan Warga Lain</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast"></div>
|
||||
|
||||
<script>
|
||||
// =============================================
|
||||
// VARIABEL GLOBAL
|
||||
// =============================================
|
||||
let pickedLL = null;
|
||||
let tempMarker = null;
|
||||
|
||||
// =============================================
|
||||
// INISIALISASI PETA
|
||||
// =============================================
|
||||
const mapLapor = L.map('map-lapor', { zoomControl: true })
|
||||
.setView([-0.0548, 109.3494], 14);
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19, attribution: '© OpenStreetMap'
|
||||
}).addTo(mapLapor);
|
||||
|
||||
const mkRed = new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize:[25,41], iconAnchor:[12,41], popupAnchor:[1,-34], shadowSize:[41,41]
|
||||
});
|
||||
|
||||
// Klik peta → simpan koordinat + tampilkan marker
|
||||
mapLapor.on('click', function(e) {
|
||||
pickedLL = e.latlng;
|
||||
|
||||
if (tempMarker) mapLapor.removeLayer(tempMarker);
|
||||
tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: mkRed })
|
||||
.addTo(mapLapor)
|
||||
.bindPopup('<div style="font-family:Plus Jakarta Sans,sans-serif;font-size:12px;font-weight:600;color:#0b2545">📍 Lokasi dipilih</div>')
|
||||
.openPopup();
|
||||
|
||||
const latStr = e.latlng.lat.toFixed(6);
|
||||
const lngStr = e.latlng.lng.toFixed(6);
|
||||
|
||||
// Update UI koordinat
|
||||
document.getElementById('coordPill').classList.add('show');
|
||||
document.getElementById('coordText').textContent = latStr + ', ' + lngStr;
|
||||
document.getElementById('locBox').classList.add('filled');
|
||||
document.getElementById('locTitle').textContent = '✅ Lokasi ditentukan';
|
||||
document.getElementById('locVal').textContent = latStr + ', ' + lngStr;
|
||||
document.getElementById('mapHint').textContent = '✅ Lokasi dipilih — klik lagi untuk mengubah';
|
||||
document.getElementById('mapHint').classList.add('picked');
|
||||
|
||||
checkSubmitReady();
|
||||
});
|
||||
|
||||
// =============================================
|
||||
// STEP INDICATOR
|
||||
// =============================================
|
||||
function updateSteps(cur) {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const el = document.getElementById('step' + i);
|
||||
el.className = 'step';
|
||||
if (i < cur) el.classList.add('done');
|
||||
else if (i === cur) el.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// VALIDASI — enable/disable tombol submit
|
||||
// =============================================
|
||||
function checkSubmitReady() {
|
||||
const ok = pickedLL
|
||||
&& document.getElementById('f_kk').value.trim()
|
||||
&& document.getElementById('f_jml').value
|
||||
&& parseInt(document.getElementById('f_jml').value) > 0
|
||||
&& document.getElementById('f_alamat').value.trim()
|
||||
&& document.getElementById('f_pelapor').value.trim();
|
||||
|
||||
document.getElementById('btnSubmit').disabled = !ok;
|
||||
|
||||
if (ok) updateSteps(4);
|
||||
else if (pickedLL) updateSteps(3);
|
||||
else updateSteps(2);
|
||||
}
|
||||
|
||||
// Pasang listener ke semua field wajib
|
||||
['f_kk','f_jml','f_alamat','f_pelapor'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', checkSubmitReady);
|
||||
});
|
||||
|
||||
// =============================================
|
||||
// FOKUS KE PETA (klik locBox di mobile)
|
||||
// =============================================
|
||||
function focusMap() {
|
||||
document.getElementById('map-lapor').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
toast('🖱️ Klik di peta untuk menentukan lokasi', '');
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// SUBMIT LAPORAN → api_laporan.php
|
||||
// =============================================
|
||||
function submitLaporan() {
|
||||
if (!pickedLL) { toast('⚠️ Pilih lokasi di peta terlebih dahulu', 'err'); return; }
|
||||
|
||||
const kk = document.getElementById('f_kk').value.trim();
|
||||
const jml = document.getElementById('f_jml').value;
|
||||
const alamat = document.getElementById('f_alamat').value.trim();
|
||||
const pelapor = document.getElementById('f_pelapor').value.trim();
|
||||
|
||||
if (!kk || !jml || !alamat || !pelapor) {
|
||||
toast('⚠️ Lengkapi semua field wajib (*)', 'err');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnSubmit');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⏳ Mengirim...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('lat', pickedLL.lat);
|
||||
fd.append('lng', pickedLL.lng);
|
||||
fd.append('nama_kk', kk);
|
||||
fd.append('jumlah_anggota', jml);
|
||||
fd.append('alamat', alamat);
|
||||
fd.append('tanggal_lahir', document.getElementById('f_tgl_lahir').value || '');
|
||||
fd.append('pendidikan_terakhir', document.getElementById('f_pendidikan').value || '');
|
||||
fd.append('pekerjaan', document.getElementById('f_pekerjaan').value.trim() || '');
|
||||
fd.append('riwayat_penyakit', document.getElementById('f_penyakit').value.trim() || '');
|
||||
fd.append('kepemilikan_jaminan', document.getElementById('f_jaminan').value || '');
|
||||
fd.append('nama_pelapor', pelapor);
|
||||
fd.append('hp_pelapor', document.getElementById('f_hp').value.trim() || '');
|
||||
fd.append('hubungan_pelapor', document.getElementById('f_hubungan').value || '');
|
||||
fd.append('keterangan', document.getElementById('f_keterangan').value.trim() || '');
|
||||
|
||||
fetch('api_laporan.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(res => {
|
||||
if (res.status === 'ok') {
|
||||
document.getElementById('sc-ref').textContent = 'ID Laporan: #' + res.id;
|
||||
document.getElementById('success-overlay').classList.add('show');
|
||||
} else {
|
||||
toast('❌ Gagal: ' + (res.pesan || 'Coba lagi'), 'err');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '📤 Kirim Laporan untuk Diverifikasi';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast('❌ Koneksi gagal. Periksa jaringan Anda.', 'err');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '📤 Kirim Laporan untuk Diverifikasi';
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// RESET — Laporan lagi
|
||||
// =============================================
|
||||
function resetForm() {
|
||||
document.getElementById('success-overlay').classList.remove('show');
|
||||
['f_kk','f_jml','f_alamat','f_tgl_lahir','f_pendidikan','f_pekerjaan',
|
||||
'f_penyakit','f_jaminan','f_pelapor','f_hp','f_hubungan','f_keterangan']
|
||||
.forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });
|
||||
|
||||
pickedLL = null;
|
||||
if (tempMarker) { mapLapor.removeLayer(tempMarker); tempMarker = null; }
|
||||
document.getElementById('coordPill').classList.remove('show');
|
||||
document.getElementById('coordText').textContent = '—';
|
||||
document.getElementById('locBox').classList.remove('filled');
|
||||
document.getElementById('locTitle').textContent = 'Belum dipilih — klik peta';
|
||||
document.getElementById('locVal').textContent = 'Klik di peta untuk menentukan koordinat';
|
||||
document.getElementById('mapHint').textContent = 'Klik titik lokasi tempat tinggal di peta';
|
||||
document.getElementById('mapHint').classList.remove('picked');
|
||||
document.getElementById('btnSubmit').disabled = true;
|
||||
document.getElementById('btnSubmit').innerHTML = '📤 Kirim Laporan untuk Diverifikasi';
|
||||
updateSteps(2);
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// TOAST — identik dengan index.php
|
||||
// =============================================
|
||||
function toast(msg, type) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.className = 'show' + (type ? ' ' + type : '');
|
||||
setTimeout(() => t.className = '', 3000);
|
||||
}
|
||||
|
||||
// QR Code untuk halaman lapor.php itu sendiri
|
||||
window.addEventListener('load', function() {
|
||||
const selfUrl = window.location.href;
|
||||
new QRCode(document.getElementById('self-qr'), {
|
||||
text: selfUrl,
|
||||
width: 100,
|
||||
height: 100,
|
||||
colorDark: '#0b2545',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
});
|
||||
// Init
|
||||
updateSteps(2);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,594 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Login — WebGIS Pemetaan Sosial</title>
|
||||
<meta name="description" content="Masuk ke platform WebGIS Pemetaan Sosial untuk mengelola data kemiskinan dan distribusi bantuan.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* ─── TOKENS ─── */
|
||||
:root {
|
||||
--blue: #2563EB;
|
||||
--blue-d: #1D4ED8;
|
||||
--blue-l: #EFF6FF;
|
||||
--blue-m: rgba(37,99,235,.10);
|
||||
--navy: #0F172A;
|
||||
--green: #10B981;
|
||||
--red: #EF4444;
|
||||
--red-l: #FEF2F2;
|
||||
--bg: #EBF0F7;
|
||||
--surface: #FFFFFF;
|
||||
--border: #E2E8F0;
|
||||
--border-l:#F1F5F9;
|
||||
--text-2: #475569;
|
||||
--text-3: #94A3B8;
|
||||
--sh: 0 4px 24px rgba(15,23,42,.10);
|
||||
--sh-lg: 0 20px 60px rgba(15,23,42,.18);
|
||||
--r: 16px;
|
||||
--r2: 12px;
|
||||
--r3: 8px;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background: var(--bg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ─── ANIMATED BACKGROUND ─── */
|
||||
.bg-scene {
|
||||
position: fixed; inset: 0;
|
||||
background: linear-gradient(135deg, #0F172A 0%, #1E3A5F 40%, #1D4ED8 80%, #2563EB 100%);
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grid map lines */
|
||||
.bg-grid {
|
||||
position: absolute; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
animation: gridPan 25s linear infinite;
|
||||
}
|
||||
@keyframes gridPan {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(48px, 48px); }
|
||||
}
|
||||
|
||||
/* Floating blobs */
|
||||
.blob {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(72px);
|
||||
opacity: 0.22;
|
||||
animation: blobFloat 12s ease-in-out infinite;
|
||||
}
|
||||
.blob-1 {
|
||||
width: 520px; height: 520px;
|
||||
background: #3B82F6;
|
||||
top: -180px; left: -140px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.blob-2 {
|
||||
width: 380px; height: 380px;
|
||||
background: #10B981;
|
||||
bottom: -100px; right: -80px;
|
||||
animation-delay: -5s;
|
||||
}
|
||||
.blob-3 {
|
||||
width: 280px; height: 280px;
|
||||
background: #6366F1;
|
||||
top: 40%; left: 55%;
|
||||
animation-delay: -9s;
|
||||
}
|
||||
@keyframes blobFloat {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(18px, -22px) scale(1.04); }
|
||||
66% { transform: translate(-12px, 14px) scale(0.97); }
|
||||
}
|
||||
|
||||
/* Floating map pin particles */
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 8px; height: 8px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.18);
|
||||
animation: particleDrift linear infinite;
|
||||
}
|
||||
@keyframes particleDrift {
|
||||
0% { transform: translateY(100vh) scale(0); opacity: 0; }
|
||||
10% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% { transform: translateY(-80px) scale(1.2); opacity: 0; }
|
||||
}
|
||||
|
||||
/* ─── LAYOUT ─── */
|
||||
.page {
|
||||
position: relative; z-index: 10;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* ─── CARD ─── */
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: rgba(255,255,255,0.97);
|
||||
border-radius: 24px;
|
||||
box-shadow: var(--sh-lg), 0 0 0 1px rgba(255,255,255,0.12);
|
||||
overflow: hidden;
|
||||
animation: cardIn 0.55s cubic-bezier(0.34,1.4,0.64,1) both;
|
||||
}
|
||||
@keyframes cardIn {
|
||||
from { opacity: 0; transform: translateY(32px) scale(0.94); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
/* Card header / hero */
|
||||
.card-hero {
|
||||
background: linear-gradient(135deg, #1D4ED8 0%, #2563EB 60%, #3B82F6 100%);
|
||||
padding: 32px 32px 28px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -30px; right: -30px;
|
||||
width: 160px; height: 160px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.06);
|
||||
}
|
||||
.card-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50px; left: -50px;
|
||||
width: 200px; height: 200px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.hero-logo {
|
||||
width: 52px; height: 52px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255,255,255,0.18);
|
||||
border: 1.5px solid rgba(255,255,255,0.28);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
backdrop-filter: blur(8px);
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.hero-logo svg { display: block; }
|
||||
|
||||
.hero-title {
|
||||
font-size: 22px; font-weight: 800;
|
||||
color: #ffffff;
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.hero-title span {
|
||||
color: rgba(255,255,255,0.65);
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Dot grid decoration */
|
||||
.hero-dots {
|
||||
position: absolute;
|
||||
top: 16px; right: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 6px);
|
||||
gap: 5px;
|
||||
z-index: 1;
|
||||
}
|
||||
.hero-dots span {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.2);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ─── CARD BODY ─── */
|
||||
.card-body {
|
||||
padding: 28px 32px 32px;
|
||||
}
|
||||
|
||||
/* Error alert */
|
||||
.alert-error {
|
||||
display: flex; align-items: flex-start; gap: 10px;
|
||||
background: var(--red-l);
|
||||
border: 1.5px solid rgba(239,68,68,0.25);
|
||||
border-radius: var(--r3);
|
||||
padding: 10px 13px;
|
||||
margin-bottom: 20px;
|
||||
animation: shakeIn 0.4s cubic-bezier(0.36,0.07,0.19,0.97);
|
||||
}
|
||||
@keyframes shakeIn {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20% { transform: translateX(-6px); }
|
||||
40% { transform: translateX(6px); }
|
||||
60% { transform: translateX(-4px); }
|
||||
80% { transform: translateX(4px); }
|
||||
}
|
||||
.alert-error svg { flex-shrink: 0; color: var(--red); margin-top: 1px; }
|
||||
.alert-error p { font-size: 12px; font-weight: 600; color: #B91C1C; line-height: 1.4; }
|
||||
|
||||
/* Section label */
|
||||
.form-label {
|
||||
font-size: 11px; font-weight: 700;
|
||||
color: var(--text-3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Input group */
|
||||
.input-group {
|
||||
position: relative;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 12px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-3);
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
.input-group:focus-within .input-icon {
|
||||
color: var(--blue);
|
||||
}
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 11px 12px 11px 38px;
|
||||
font-size: 13px; font-family: inherit;
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: var(--r3);
|
||||
background: var(--border-l);
|
||||
color: var(--navy);
|
||||
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.input-group input:focus {
|
||||
border-color: var(--blue);
|
||||
background: white;
|
||||
box-shadow: 0 0 0 3px var(--blue-m);
|
||||
}
|
||||
.input-group input::placeholder {
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
/* Password toggle */
|
||||
.pw-toggle {
|
||||
position: absolute;
|
||||
right: 11px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none; border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-3);
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
.pw-toggle:hover {
|
||||
color: var(--blue);
|
||||
background: var(--blue-m);
|
||||
}
|
||||
.pw-toggle svg { display: block; }
|
||||
|
||||
/* Submit button */
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-top: 4px;
|
||||
border-radius: var(--r3);
|
||||
border: none;
|
||||
background: linear-gradient(135deg, var(--blue) 0%, var(--blue-d) 100%);
|
||||
color: white;
|
||||
font-family: inherit;
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
box-shadow: 0 4px 14px rgba(37,99,235,0.35);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn-login::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 60%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.btn-login:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 20px rgba(37,99,235,0.45);
|
||||
}
|
||||
.btn-login:active {
|
||||
transform: translateY(0) scale(0.98);
|
||||
}
|
||||
.btn-login svg { display: block; flex-shrink: 0; }
|
||||
|
||||
/* Divider */
|
||||
.divider {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
margin: 20px 0 16px;
|
||||
}
|
||||
.divider::before, .divider::after {
|
||||
content: ''; flex: 1; height: 1px; background: var(--border);
|
||||
}
|
||||
.divider span {
|
||||
font-size: 11px; font-weight: 600;
|
||||
color: var(--text-3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Info badges */
|
||||
.info-row {
|
||||
display: flex; gap: 7px;
|
||||
}
|
||||
.info-badge {
|
||||
flex: 1;
|
||||
background: var(--border-l);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r3);
|
||||
padding: 8px 10px;
|
||||
display: flex; align-items: center; gap: 7px;
|
||||
}
|
||||
.ib-icon {
|
||||
width: 26px; height: 26px;
|
||||
border-radius: 7px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ib-icon svg { display: block; }
|
||||
.ib-text { font-size: 10px; font-weight: 600; color: var(--text-2); line-height: 1.3; }
|
||||
.ib-text b { display: block; color: var(--navy); font-size: 11px; }
|
||||
|
||||
/* Footer note */
|
||||
.card-footer {
|
||||
padding: 14px 32px 18px;
|
||||
border-top: 1px solid var(--border-l);
|
||||
text-align: center;
|
||||
background: var(--border-l);
|
||||
}
|
||||
.card-footer p {
|
||||
font-size: 11px; color: var(--text-3); font-weight: 500;
|
||||
display: flex; align-items: center; justify-content: center; gap: 5px;
|
||||
}
|
||||
.card-footer p svg { color: var(--blue); flex-shrink: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Animated Background -->
|
||||
<div class="bg-scene">
|
||||
<div class="bg-grid"></div>
|
||||
<div class="blob blob-1"></div>
|
||||
<div class="blob blob-2"></div>
|
||||
<div class="blob blob-3"></div>
|
||||
|
||||
<!-- Floating particles -->
|
||||
<div class="particle" style="left:12%;animation-duration:9s;animation-delay:0s;width:5px;height:5px;"></div>
|
||||
<div class="particle" style="left:25%;animation-duration:13s;animation-delay:-3s;width:8px;height:8px;"></div>
|
||||
<div class="particle" style="left:38%;animation-duration:8s;animation-delay:-6s;width:4px;height:4px;"></div>
|
||||
<div class="particle" style="left:52%;animation-duration:11s;animation-delay:-1s;width:6px;height:6px;"></div>
|
||||
<div class="particle" style="left:67%;animation-duration:14s;animation-delay:-8s;width:5px;height:5px;"></div>
|
||||
<div class="particle" style="left:80%;animation-duration:10s;animation-delay:-4s;width:9px;height:9px;"></div>
|
||||
<div class="particle" style="left:91%;animation-duration:7s;animation-delay:-2s;width:4px;height:4px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
<div class="card">
|
||||
|
||||
<!-- Hero Header -->
|
||||
<div class="card-hero">
|
||||
<div class="hero-dots">
|
||||
<?php for($i=0;$i<15;$i++) echo '<span></span>'; ?>
|
||||
</div>
|
||||
<div class="hero-logo">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="hero-title">
|
||||
WebGIS Pemetaan Sosial
|
||||
<span>Platform Manajemen Data Kemiskinan & Distribusi Bantuan</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Body -->
|
||||
<div class="card-body">
|
||||
|
||||
<?php if (!empty($_GET['error'])): ?>
|
||||
<div class="alert-error">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<line x1="12" y1="8" x2="12" y2="12"/>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||
</svg>
|
||||
<p>
|
||||
<?php
|
||||
$err = htmlspecialchars($_GET['error']);
|
||||
if ($err === 'invalid') echo 'Username atau password salah. Silakan coba lagi.';
|
||||
elseif ($err === 'empty') echo 'Username dan password tidak boleh kosong.';
|
||||
else echo $err;
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="auth_proses.php" method="POST" id="login-form" onsubmit="handleSubmit(this)">
|
||||
|
||||
<!-- Username -->
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<div class="input-group">
|
||||
<span class="input-icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Masukkan username"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<div class="input-group" style="margin-bottom:20px">
|
||||
<span class="input-icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Masukkan password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
>
|
||||
<button type="button" class="pw-toggle" id="pw-toggle" onclick="togglePw()" title="Tampilkan password">
|
||||
<svg id="eye-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<button type="submit" class="btn-login" id="btn-submit">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4"/>
|
||||
<polyline points="10 17 15 12 10 7"/>
|
||||
<line x1="15" y1="12" x2="3" y2="12"/>
|
||||
</svg>
|
||||
<span id="btn-text">Masuk ke Sistem</span>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="divider"><span>Akses Tersedia</span></div>
|
||||
|
||||
<!-- Role info -->
|
||||
<div class="info-row">
|
||||
<div class="info-badge">
|
||||
<div class="ib-icon" style="background:#EFF6FF">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ib-text"><b>Admin</b>Kelola semua data</div>
|
||||
</div>
|
||||
<div class="info-badge">
|
||||
<div class="ib-icon" style="background:#FFFBEB">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#D97706" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="8" r="5"/>
|
||||
<path d="M3 21h18a9 9 0 00-18 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ib-text"><b>Walikota</b>Pantau & analisis</div>
|
||||
</div>
|
||||
<div class="info-badge">
|
||||
<div class="ib-icon" style="background:#ECFDF5">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#10B981" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 00-3-3.87"/>
|
||||
<path d="M16 3.13a4 4 0 010 7.75"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ib-text"><b>Warga</b>Laporan & peta</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="card-footer">
|
||||
<p>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
||||
</svg>
|
||||
Sistem dilindungi enkripsi — WebGIS Pemetaan Sosial © <?= date('Y') ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ─── Toggle Password Visibility ─── */
|
||||
function togglePw() {
|
||||
const inp = document.getElementById('password');
|
||||
const icon = document.getElementById('eye-icon');
|
||||
const isHidden = inp.type === 'password';
|
||||
inp.type = isHidden ? 'text' : 'password';
|
||||
icon.innerHTML = isHidden
|
||||
? /* eye-off */`<path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>`
|
||||
: /* eye */`<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>`;
|
||||
}
|
||||
|
||||
/* ─── Loading state on submit ─── */
|
||||
function handleSubmit(form) {
|
||||
const btn = document.getElementById('btn-submit');
|
||||
const text = document.getElementById('btn-text');
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.8';
|
||||
text.textContent = 'Memproses...';
|
||||
|
||||
// Spinner icon
|
||||
btn.querySelector('svg').innerHTML = `<circle cx="12" cy="12" r="9" stroke="rgba(255,255,255,.3)" stroke-width="2.5" fill="none"/><path d="M12 3a9 9 0 019 9" stroke="white" stroke-width="2.5" stroke-linecap="round" fill="none" style="transform-origin:center;animation:spin .8s linear infinite"/>`;
|
||||
|
||||
// Re-enable if PHP redirects back (error)
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
btn.style.opacity = '1';
|
||||
text.textContent = 'Masuk ke Sistem';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/* ─── Spin keyframe ─── */
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
|
||||
document.head.appendChild(style);
|
||||
|
||||
/* ─── Enter key focus flow ─── */
|
||||
document.getElementById('username').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
document.getElementById('password').focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_destroy(); // Menghapus semua data sesi
|
||||
header("Location: login.php"); // Kembali ke halaman login
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
$pdo = new PDO('mysql:host=localhost;dbname=webgis_baru;charset=utf8', 'root', '');
|
||||
$stmt = $pdo->query('DESCRIBE donasi_digital');
|
||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 61 KiB |