From 1bf000979c1eecb2a0f9feffc58ad6ffcde01592 Mon Sep 17 00:00:00 2001 From: Requestha Amorine Montass Date: Fri, 12 Jun 2026 17:58:06 +0700 Subject: [PATCH] Deploy WebGIS 01-05 dengan Docker --- .dockerignore | 8 + 01/01_spbudb.sql | 79 ++ 01/ambil_data.php | 40 + 01/assets/css/style.css | 354 +++++ 01/assets/js/script.js | 590 +++++++++ 01/edit.php | 105 ++ 01/hapus.php | 48 + 01/index.php | 422 ++++++ 01/koneksi.php | 21 + 01/tambah.php | 103 ++ 02/02_spbudb.sql | 143 ++ 02/ambil_data.php | 69 + 02/edit.php | 82 ++ 02/hapus.php | 60 + 02/index.php | 1136 ++++++++++++++++ 02/koneksi.php | 23 + 02/setup_database.sql | 70 + 02/tambah.php | 163 +++ 03/03_webgisdb.sql | 106 ++ 03/ambil_data.php | 60 + 03/database.sql | 26 + 03/edit.php | 129 ++ 03/hapus.php | 80 ++ 03/index.php | 1177 +++++++++++++++++ 03/koneksi.php | 31 + 03/tambah.php | 145 ++ 04/04_spbudb.sql | 77 ++ 04/ambil_data.php | 40 + 04/edit.php | 115 ++ 04/hapus.php | 53 + 04/index.php | 793 +++++++++++ 04/koneksi.php | 20 + 04/schema.sql | 33 + 04/tambah.php | 116 ++ 05/05_povertydb.sql | 373 ++++++ ...ASI_LOCALHOST_SAMPAI_HOSTING_PROJECT_05.md | 723 ++++++++++ 05/RANGKUMAN_PENTING_PROJECT_05.md | 230 ++++ ...L_SRS_WEBGIS_POVERTY_MAPPING_PROJECT_05.md | 632 +++++++++ ...UTORIAL_CARA_PAKAI_APLIKASI_PROJECT_05.txt | 510 +++++++ 05/ajukan_rumah_ibadah.php | 49 + 05/ambil_data_peta.php | 55 + 05/ambil_laporan_penyaluran.php | 138 ++ 05/ambil_program_bantuan.php | 28 + 05/ambil_riwayat.php | 44 + 05/ambil_user.php | 76 ++ 05/auth.php | 314 +++++ 05/dashboard.php | 436 ++++++ 05/edit_penerima.php | 70 + 05/edit_program_bantuan.php | 36 + 05/edit_user.php | 86 ++ 05/hapus_penerima.php | 35 + 05/hapus_program_bantuan.php | 32 + 05/hapus_rumah_ibadah.php | 36 + 05/hapus_user.php | 71 + 05/index.php | 286 ++++ 05/koneksi.php | 23 + 05/login.php | 53 + 05/logout.php | 7 + 05/register.php | 58 + 05/tambah_penerima.php | 83 ++ 05/tambah_penyaluran.php | 76 ++ 05/tambah_program_bantuan.php | 48 + 05/tambah_user.php | 63 + 05/update_status_penerima.php | 56 + 05/update_status_rumah_ibadah.php | 63 + Dockerfile | 9 + README_DEPLOY.md | 72 + db/Dockerfile | 2 + db/README.md | 2 + db/init/10_01_spbudb.sql | 82 ++ db/init/20_02_spbudb.sql | 146 ++ db/init/30_03_webgisdb.sql | 109 ++ db/init/40_04_spbudb.sql | 80 ++ db/init/50_05_povertydb.sql | 378 ++++++ docker-compose.yml | 29 + index.php | 85 ++ 76 files changed, 12301 insertions(+) create mode 100644 .dockerignore create mode 100644 01/01_spbudb.sql create mode 100644 01/ambil_data.php create mode 100644 01/assets/css/style.css create mode 100644 01/assets/js/script.js create mode 100644 01/edit.php create mode 100644 01/hapus.php create mode 100644 01/index.php create mode 100644 01/koneksi.php create mode 100644 01/tambah.php create mode 100644 02/02_spbudb.sql create mode 100644 02/ambil_data.php create mode 100644 02/edit.php create mode 100644 02/hapus.php create mode 100644 02/index.php create mode 100644 02/koneksi.php create mode 100644 02/setup_database.sql create mode 100644 02/tambah.php create mode 100644 03/03_webgisdb.sql create mode 100644 03/ambil_data.php create mode 100644 03/database.sql create mode 100644 03/edit.php create mode 100644 03/hapus.php create mode 100644 03/index.php create mode 100644 03/koneksi.php create mode 100644 03/tambah.php create mode 100644 04/04_spbudb.sql create mode 100644 04/ambil_data.php create mode 100644 04/edit.php create mode 100644 04/hapus.php create mode 100644 04/index.php create mode 100644 04/koneksi.php create mode 100644 04/schema.sql create mode 100644 04/tambah.php create mode 100644 05/05_povertydb.sql create mode 100644 05/PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING_PROJECT_05.md create mode 100644 05/RANGKUMAN_PENTING_PROJECT_05.md create mode 100644 05/SKPL_SRS_WEBGIS_POVERTY_MAPPING_PROJECT_05.md create mode 100644 05/TUTORIAL_CARA_PAKAI_APLIKASI_PROJECT_05.txt create mode 100644 05/ajukan_rumah_ibadah.php create mode 100644 05/ambil_data_peta.php create mode 100644 05/ambil_laporan_penyaluran.php create mode 100644 05/ambil_program_bantuan.php create mode 100644 05/ambil_riwayat.php create mode 100644 05/ambil_user.php create mode 100644 05/auth.php create mode 100644 05/dashboard.php create mode 100644 05/edit_penerima.php create mode 100644 05/edit_program_bantuan.php create mode 100644 05/edit_user.php create mode 100644 05/hapus_penerima.php create mode 100644 05/hapus_program_bantuan.php create mode 100644 05/hapus_rumah_ibadah.php create mode 100644 05/hapus_user.php create mode 100644 05/index.php create mode 100644 05/koneksi.php create mode 100644 05/login.php create mode 100644 05/logout.php create mode 100644 05/register.php create mode 100644 05/tambah_penerima.php create mode 100644 05/tambah_penyaluran.php create mode 100644 05/tambah_program_bantuan.php create mode 100644 05/tambah_user.php create mode 100644 05/update_status_penerima.php create mode 100644 05/update_status_rumah_ibadah.php create mode 100644 Dockerfile create mode 100644 README_DEPLOY.md create mode 100644 db/Dockerfile create mode 100644 db/README.md create mode 100644 db/init/10_01_spbudb.sql create mode 100644 db/init/20_02_spbudb.sql create mode 100644 db/init/30_03_webgisdb.sql create mode 100644 db/init/40_04_spbudb.sql create mode 100644 db/init/50_05_povertydb.sql create mode 100644 docker-compose.yml create mode 100644 index.php diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e5181d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +**/.git +__MACOSX +*.zip +*.rar +*.7z +.DS_Store +Thumbs.db diff --git a/01/01_spbudb.sql b/01/01_spbudb.sql new file mode 100644 index 0000000..cb669b5 --- /dev/null +++ b/01/01_spbudb.sql @@ -0,0 +1,79 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `01_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') NOT NULL, + `buka_24_jam` enum('ya','tidak') NOT NULL, + `status_operasional` enum('buka','tutup') NOT NULL, + `nomor_hp` varchar(20) NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(1, 'SPBU IMBON', 'ramai', 'ya', 'buka', '0815497422156', -0.0469843, 109.354544, '2026-06-08 02:48:42', '2026-06-08 02:48:42'), +(3, 'SPBU Gertak 1', 'ramai', 'tidak', 'buka', '0815497422156', -0.0471444, 109.3556786, '2026-06-08 02:53:40', '2026-06-08 02:53:40'), +(4, 'SPBU IMBON', 'ramai', 'ya', 'tutup', '0815497422156', -0.0472983, 109.3563867, '2026-06-08 02:54:03', '2026-06-08 02:54:03'), +(5, 'SPBU IMBON', 'tidak_ramai', 'tidak', 'buka', '0815497422156', -0.0476848, 109.3552065, '2026-06-08 02:54:20', '2026-06-08 02:54:50'), +(6, 'SPBU Gertak 2', 'tidak_ramai', 'ya', 'buka', '087894587965', -0.0471113, 109.3553186, '2026-06-08 07:32:27', '2026-06-08 07:32:27'), +(7, 'SPBU OSO', 'tidak_ramai', 'tidak', 'buka', '026598745234', -0.0449249, 109.3367475, '2026-06-08 15:38:19', '2026-06-08 15:38:19'), +(8, 'SPBU GOLDEN TULIP', 'ramai', 'tidak', 'buka', '0564123', -0.0343809, 109.3319061, '2026-06-08 15:39:22', '2026-06-08 15:39:22'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/01/ambil_data.php b/01/ambil_data.php new file mode 100644 index 0000000..739cf2e --- /dev/null +++ b/01/ambil_data.php @@ -0,0 +1,40 @@ +query("SELECT * FROM spbu ORDER BY id DESC"); + +// Cek apakah query berhasil +if (!$result) { + echo json_encode(['status' => 'error', 'message' => 'Gagal mengambil data: ' . $koneksi->error]); + exit; +} + +// ── Kumpulkan semua baris data ke dalam array ───────────────────────────────── +$data = []; +while ($row = $result->fetch_assoc()) { + // Konversi latitude & longitude ke tipe float agar JSON valid + $row['latitude'] = (float) $row['latitude']; + $row['longitude'] = (float) $row['longitude']; + $data[] = $row; +} + +// ── Kembalikan data sebagai JSON ────────────────────────────────────────────── +echo json_encode([ + 'status' => 'success', + 'data' => $data +]); + +$koneksi->close(); +?> diff --git a/01/assets/css/style.css b/01/assets/css/style.css new file mode 100644 index 0000000..05db6f2 --- /dev/null +++ b/01/assets/css/style.css @@ -0,0 +1,354 @@ +/* ============================================================ + style.css — WebGIS SPBU + Tampilan sederhana, bersih, dan fungsional + ============================================================ */ + +/* ── Import Google Fonts ──────────────────────────────────── */ +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap'); + +/* ── CSS Variables (tema warna) ───────────────────────────── */ +:root { + --primary: #1a56db; + --primary-dk: #1341b0; + --danger: #e02424; + --danger-dk: #b91c1c; + --success: #057a55; + --bg: #f0f4f8; + --panel: #ffffff; + --border: #d1d9e0; + --text: #1a202c; + --text-muted: #6b7280; + --shadow: 0 4px 24px rgba(0,0,0,0.10); + --radius: 10px; + --font: 'Plus Jakarta Sans', sans-serif; +} + +/* ── Reset & Base ─────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +body { + font-family: var(--font); + background: var(--bg); + color: var(--text); + height: 100%; /* ← pastikan ada height: 100% */ + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ── Header ───────────────────────────────────────────────── */ +header { + background: var(--primary); + color: #fff; + padding: 0 24px; + height: 58px; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 2px 10px rgba(26,86,219,0.35); + z-index: 1000; + flex-shrink: 0; +} + +header h1 { + font-size: 1.15rem; + font-weight: 700; + letter-spacing: -0.01em; + display: flex; + align-items: center; + gap: 10px; +} + +header h1 .icon { font-size: 1.3rem; } + +.header-hint { + font-size: 0.78rem; + background: rgba(255,255,255,0.18); + border: 1px solid rgba(255,255,255,0.3); + border-radius: 20px; + padding: 4px 12px; + display: flex; + align-items: center; + gap: 6px; +} + +/* ── Map Container ────────────────────────────────────────── */ +#map { + flex: 1; + width: 100%; + min-height: 0; /* ← TAMBAHKAN BARIS INI */ + cursor: crosshair; +} + +/* ── Legend ───────────────────────────────────────────────── */ +.legend { + background: var(--panel); + border-radius: var(--radius); + padding: 12px 14px; + box-shadow: var(--shadow); + font-size: 0.78rem; + min-width: 170px; +} + +.legend h4 { + font-size: 0.8rem; + font-weight: 700; + margin-bottom: 8px; + color: var(--text); + border-bottom: 1px solid var(--border); + padding-bottom: 5px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 5px; + line-height: 1.3; +} + +.legend-dot { + width: 14px; + height: 14px; + border-radius: 50%; + flex-shrink: 0; + border: 2px solid rgba(0,0,0,0.25); +} + +.dot-orange { background: #f97316; } +.dot-green { background: #22c55e; } +.dot-red { background: #ef4444; } +.dot-blue { background: #3b82f6; } +.dot-black { background: #1f2937; } + +/* ── Leaflet Popup Overrides ─────────────────────────────── */ +.leaflet-popup-content-wrapper { + border-radius: var(--radius) !important; + padding: 0 !important; + overflow: hidden; + box-shadow: var(--shadow) !important; + border: 1px solid var(--border); + min-width: 280px; +} + +.leaflet-popup-content { + margin: 0 !important; + width: auto !important; +} + +.leaflet-popup-tip-container { margin-top: -1px; } + +/* ── Popup: Header ────────────────────────────────────────── */ +.popup-header { + background: var(--primary); + color: #fff; + padding: 12px 16px; + font-weight: 700; + font-size: 0.92rem; + display: flex; + align-items: center; + gap: 8px; +} + +/* ── Popup: Body ──────────────────────────────────────────── */ +.popup-body { + padding: 14px 16px; + background: var(--panel); +} + +/* ── Popup: Info Table (detail view) ─────────────────────── */ +.info-table { + width: 100%; + border-collapse: collapse; + font-size: 0.83rem; + margin-bottom: 12px; +} + +.info-table tr td { + padding: 5px 4px; + vertical-align: top; +} + +.info-table tr td:first-child { + color: var(--text-muted); + white-space: nowrap; + width: 110px; + font-weight: 500; +} + +.info-table tr td:last-child { + font-weight: 600; + color: var(--text); +} + +/* ── Badge status ─────────────────────────────────────────── */ +.badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; +} +.badge-ramai { background: #fef3c7; color: #92400e; } +.badge-tidakramai{ background: #ecfdf5; color: #065f46; } +.badge-ya { background: #eff6ff; color: #1e40af; } +.badge-tidak { background: #f9fafb; color: #374151; } +.badge-buka { background: #d1fae5; color: #065f46; } +.badge-tutup { background: #fee2e2; color: #991b1b; } + +/* ── Form Controls ────────────────────────────────────────── */ +.form-group { + margin-bottom: 10px; +} + +.form-group label { + display: block; + font-size: 0.79rem; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.form-group input[type="text"], +.form-group select { + width: 100%; + padding: 7px 10px; + border: 1.5px solid var(--border); + border-radius: 7px; + font-family: var(--font); + font-size: 0.85rem; + color: var(--text); + background: #f8fafc; + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; +} + +.form-group input:focus, +.form-group select:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(26,86,219,0.12); + background: #fff; +} + +/* ── Buttons ──────────────────────────────────────────────── */ +.btn-row { + display: flex; + gap: 8px; + margin-top: 12px; +} + +.btn { + flex: 1; + padding: 8px 12px; + border: none; + border-radius: 7px; + font-family: var(--font); + font-size: 0.83rem; + font-weight: 600; + cursor: pointer; + transition: background 0.15s, transform 0.1s; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; +} + +.btn:active { transform: scale(0.97); } + +.btn-primary { + background: var(--primary); + color: #fff; +} +.btn-primary:hover { background: var(--primary-dk); } + +.btn-danger { + background: var(--danger); + color: #fff; +} +.btn-danger:hover { background: var(--danger-dk); } + +.btn-secondary { + background: #f1f5f9; + color: var(--text); + border: 1.5px solid var(--border); +} +.btn-secondary:hover { background: #e2e8f0; } + +.btn-success { + background: var(--success); + color: #fff; +} +.btn-success:hover { background: #046c4e; } + +/* ── Divider ──────────────────────────────────────────────── */ +.divider { + border: none; + border-top: 1px solid var(--border); + margin: 10px 0; +} + +/* ── Toast Notification ───────────────────────────────────── */ +#toast { + position: fixed; + bottom: 28px; + left: 50%; + transform: translateX(-50%) translateY(80px); + background: #1f2937; + color: #fff; + padding: 10px 22px; + border-radius: 30px; + font-size: 0.85rem; + font-weight: 500; + z-index: 9999; + opacity: 0; + transition: transform 0.3s ease, opacity 0.3s ease; + pointer-events: none; + white-space: nowrap; + box-shadow: 0 4px 20px rgba(0,0,0,0.25); +} + +#toast.show { + opacity: 1; + transform: translateX(-50%) translateY(0); +} +#toast.toast-success { background: #057a55; } +#toast.toast-error { background: #e02424; } + +/* ── Loading Overlay ──────────────────────────────────────── */ +#loading { + display: none; + position: fixed; + inset: 0; + background: rgba(255,255,255,0.55); + z-index: 8000; + align-items: center; + justify-content: center; +} +#loading.show { display: flex; } + +.spinner { + width: 38px; + height: 38px; + border: 4px solid var(--border); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.7s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* ── Koordinat display ────────────────────────────────────── */ +.coord-display { + background: #f0f9ff; + border: 1px solid #bae6fd; + border-radius: 7px; + padding: 7px 10px; + font-size: 0.78rem; + color: #0369a1; + margin-bottom: 10px; + display: flex; + gap: 12px; + align-items: center; +} +.coord-display span { font-weight: 600; } diff --git a/01/assets/js/script.js b/01/assets/js/script.js new file mode 100644 index 0000000..d7becb7 --- /dev/null +++ b/01/assets/js/script.js @@ -0,0 +1,590 @@ +/** + * script.js — WebGIS SPBU + * File ini berisi seluruh logika JavaScript: + * - Inisialisasi peta Leaflet + * - Klik peta → popup form tambah data + * - Ambil data dari database & tampilkan marker + * - Tambah, edit, hapus data via AJAX/fetch + * - Warna marker sesuai kondisi SPBU + */ + +/* ══════════════════════════════════════════════════════════ + 1. VARIABEL GLOBAL + ══════════════════════════════════════════════════════════ */ + +// Objek yang menyimpan semua marker di peta, dengan key = id SPBU +const markersMap = {}; + +// Referensi popup sementara saat user klik peta (sebelum data disimpan) +let tempPopup = null; + +/* ══════════════════════════════════════════════════════════ + 2. INISIALISASI PETA LEAFLET + ══════════════════════════════════════════════════════════ */ + +// Buat instance peta, pusatkan ke Informatika +const informatikaUntan = [-0.05566422376778593, 109.34854622732368]; +const map = L.map('map').setView(informatikaUntan, 17); + +// Tambahkan tile layer OpenStreetMap +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' +}).addTo(map); + +/* ══════════════════════════════════════════════════════════ + 3. LEGEND PETA + ══════════════════════════════════════════════════════════ */ + +const legend = L.control({ position: 'bottomright' }); +legend.onAdd = function () { + const div = L.DomUtil.create('div', 'legend'); + div.innerHTML = ` +

🗺️ Keterangan Marker

+
Ramai · 24 Jam · Buka
+
Tidak Ramai · 24 Jam · Buka
+
Ramai · Tidak 24 Jam · Buka
+
Tidak Ramai · Tidak 24 Jam · Buka
+
Tutup (apapun kondisinya)
+ `; + return div; +}; +legend.addTo(map); + +/* ══════════════════════════════════════════════════════════ + 4. FUNGSI UTILITAS + ══════════════════════════════════════════════════════════ */ + +/** + * Tampilkan toast notification. + * @param {string} pesan - Teks yang ditampilkan + * @param {string} tipe - 'success' | 'error' + */ +function tampilkanToast(pesan, tipe = 'success') { + const toast = document.getElementById('toast'); + toast.textContent = pesan; + toast.className = `show toast-${tipe}`; + setTimeout(() => { toast.className = ''; }, 3000); +} + +/** Tampilkan loading spinner */ +function showLoading() { + document.getElementById('loading').classList.add('show'); +} + +/** Sembunyikan loading spinner */ +function hideLoading() { + document.getElementById('loading').classList.remove('show'); +} + +/** + * Tentukan warna marker berdasarkan kondisi SPBU. + * Aturan: + * - tutup → hitam + * - ramai + 24jam + buka → orange + * - tidak_ramai + 24jam + buka → hijau + * - ramai + tidak 24jam + buka → merah + * - tidak_ramai + tidak 24jam + buka → biru + */ +function getWarnaMarker(spbu) { + if (spbu.status_operasional === 'tutup') return 'black'; + if (spbu.status_ramai === 'ramai' && spbu.buka_24_jam === 'ya') return 'orange'; + if (spbu.status_ramai === 'tidak_ramai' && spbu.buka_24_jam === 'ya') return 'green'; + if (spbu.status_ramai === 'ramai' && spbu.buka_24_jam === 'tidak') return 'red'; + if (spbu.status_ramai === 'tidak_ramai' && spbu.buka_24_jam === 'tidak') return 'blue'; + return 'gray'; // fallback +} + +/** + * Buat ikon marker berwarna menggunakan SVG inline. + * @param {string} warna - Nama warna CSS + */ +function buatIkon(warna) { + // Palet warna hex + const palet = { + orange: '#f97316', green: '#22c55e', red: '#ef4444', + blue: '#3b82f6', black: '#1f2937', gray: '#9ca3af' + }; + const hex = palet[warna] || '#9ca3af'; + + // SVG pin marker + const svg = ` + + + + `; + + return L.divIcon({ + html: svg, + className: '', // Hapus class default Leaflet + iconSize: [28, 42], + iconAnchor: [14, 42], // Titik anchor di ujung bawah pin + popupAnchor: [0, -42], // Popup muncul di atas marker + }); +} + +/** + * Format label yang lebih ramah untuk ditampilkan. + */ +function labelRamai(val) { return val === 'ramai' ? 'Ramai' : 'Tidak Ramai'; } +function labelBuka24(val) { return val === 'ya' ? 'Ya' : 'Tidak'; } +function labelOperasional(val) { return val === 'buka' ? 'Buka' : 'Tutup'; } + +function badgeRamai(val) { + return val === 'ramai' + ? `Ramai` + : `Tidak Ramai`; +} +function badgeBuka24(val) { + return val === 'ya' + ? `Ya (24 Jam)` + : `Tidak`; +} +function badgeOperasional(val) { + return val === 'buka' + ? `Buka` + : `Tutup`; +} + +/* ══════════════════════════════════════════════════════════ + 5. BUAT KONTEN POPUP DETAIL DATA SPBU + ══════════════════════════════════════════════════════════ */ + +/** + * Menghasilkan HTML untuk popup detail SPBU. + * Ditampilkan ketika marker di-klik. + */ +function buatPopupDetail(spbu) { + return ` + + `; +} + +/* ══════════════════════════════════════════════════════════ + 6. AMBIL DATA DARI DATABASE & TAMPILKAN MARKER + ══════════════════════════════════════════════════════════ */ + +/** + * Ambil semua data SPBU dari server lalu tampilkan sebagai marker di peta. + */ +async function muatSemuaMarker() { + try { + const res = await fetch('ambil_data.php'); + const json = await res.json(); + + if (json.status !== 'success') { + console.error('Gagal ambil data:', json.message); + return; + } + + // Hapus marker lama yang sudah ada di peta + Object.values(markersMap).forEach(m => map.removeLayer(m)); + // Kosongkan objek referensi marker + for (const key in markersMap) delete markersMap[key]; + + // Buat marker baru untuk setiap SPBU + json.data.forEach(spbu => { + tambahMarker(spbu); + }); + + } catch (err) { + console.error('Error fetch ambil_data:', err); + } +} + +/** + * Tambahkan satu marker ke peta dan daftarkan ke markersMap. + * @param {object} spbu - Data SPBU dari database + */ +function tambahMarker(spbu) { + const warna = getWarnaMarker(spbu); + const ikon = buatIkon(warna); + + const marker = L.marker([spbu.latitude, spbu.longitude], { icon: ikon }) + .addTo(map) + .bindPopup(buatPopupDetail(spbu), { maxWidth: 320 }); + + // Simpan referensi marker + markersMap[spbu.id] = marker; +} + +/* ══════════════════════════════════════════════════════════ + 7. KLIK PETA → FORM TAMBAH DATA SPBU + ══════════════════════════════════════════════════════════ */ + +// Event: saat user klik di peta +map.on('click', function (e) { + const lat = e.latlng.lat.toFixed(7); + const lng = e.latlng.lng.toFixed(7); + + // Tutup popup sementara yang mungkin masih terbuka + if (tempPopup) { + map.closePopup(tempPopup); + tempPopup = null; + } + + // Konten form tambah data (lat & lng disembunyikan dari user) + const formHtml = ` + + `; + + // Buka popup di lokasi yang diklik + tempPopup = L.popup({ maxWidth: 340 }) + .setLatLng(e.latlng) + .setContent(formHtml) + .openOn(map); +}); + +/* ══════════════════════════════════════════════════════════ + 8. SIMPAN DATA SPBU BARU (TAMBAH) + ══════════════════════════════════════════════════════════ */ + +/** + * Kirim data form tambah ke server via fetch (AJAX). + * Dipanggil oleh tombol "Simpan" di popup form tambah. + * @param {number} lat - Latitude dari klik peta + * @param {number} lng - Longitude dari klik peta + */ +async function simpanData(lat, lng) { + // Ambil nilai dari form + const nama = document.getElementById('f-nama')?.value.trim(); + const ramai = document.getElementById('f-ramai')?.value; + const jam24 = document.getElementById('f-24jam')?.value; + const operasional = document.getElementById('f-operasional')?.value; + const hp = document.getElementById('f-hp')?.value.trim(); + + // Validasi input tidak boleh kosong + if (!nama || !ramai || !jam24 || !operasional || !hp) { + tampilkanToast('⚠️ Semua field wajib diisi!', 'error'); + return; + } + + // Siapkan data untuk dikirim + const formData = new FormData(); + formData.append('nama_spbu', nama); + formData.append('status_ramai', ramai); + formData.append('buka_24_jam', jam24); + formData.append('status_operasional', operasional); + formData.append('nomor_hp', hp); + formData.append('latitude', lat); + formData.append('longitude', lng); + + showLoading(); + + try { + const res = await fetch('tambah.php', { method: 'POST', body: formData }); + const json = await res.json(); + + if (json.status === 'success') { + // Tutup popup form + map.closePopup(); + tempPopup = null; + + // Langsung tambah marker baru tanpa perlu refresh halaman + tambahMarker(json.data); + + // Geser peta ke lokasi SPBU baru dan buka popupnya + map.panTo([json.data.latitude, json.data.longitude]); + markersMap[json.data.id].openPopup(); + + tampilkanToast('✅ Data SPBU berhasil ditambahkan!', 'success'); + } else { + tampilkanToast('❌ ' + json.message, 'error'); + } + } catch (err) { + tampilkanToast('❌ Gagal terhubung ke server.', 'error'); + console.error(err); + } finally { + hideLoading(); + } +} + +/* ══════════════════════════════════════════════════════════ + 9. BUKA FORM EDIT DATA SPBU + ══════════════════════════════════════════════════════════ */ + +/** + * Ganti isi popup marker dengan form edit. + * Latitude dan longitude TIDAK ditampilkan dan TIDAK bisa diedit. + * @param {number} id - ID SPBU yang akan diedit + */ +async function bukaFormEdit(id) { + showLoading(); + + try { + // Ambil data terbaru dari server + const res = await fetch('ambil_data.php'); + const json = await res.json(); + hideLoading(); + + if (json.status !== 'success') { + tampilkanToast('❌ Gagal mengambil data.', 'error'); + return; + } + + // Cari data SPBU yang sesuai ID + const spbu = json.data.find(s => s.id == id); + if (!spbu) { + tampilkanToast('❌ Data tidak ditemukan.', 'error'); + return; + } + + // Buat konten form edit (lat & lng disembunyikan) + const formEditHtml = ` + + `; + + // Update isi popup yang sedang terbuka + markersMap[id].setPopupContent(formEditHtml).openPopup(); + + } catch (err) { + hideLoading(); + tampilkanToast('❌ Gagal terhubung ke server.', 'error'); + console.error(err); + } +} + +/* ══════════════════════════════════════════════════════════ + 10. SIMPAN PERUBAHAN EDIT DATA SPBU + ══════════════════════════════════════════════════════════ */ + +/** + * Kirim data form edit ke server. + * Latitude & longitude tidak dikirim → tidak berubah di database. + * @param {number} id - ID SPBU yang diedit + */ +async function simpanEdit(id) { + const nama = document.getElementById('e-nama')?.value.trim(); + const ramai = document.getElementById('e-ramai')?.value; + const jam24 = document.getElementById('e-24jam')?.value; + const operasional = document.getElementById('e-operasional')?.value; + const hp = document.getElementById('e-hp')?.value.trim(); + + if (!nama || !ramai || !jam24 || !operasional || !hp) { + tampilkanToast('⚠️ Semua field wajib diisi!', 'error'); + return; + } + + const formData = new FormData(); + formData.append('id', id); + formData.append('nama_spbu', nama); + formData.append('status_ramai', ramai); + formData.append('buka_24_jam', jam24); + formData.append('status_operasional', operasional); + formData.append('nomor_hp', hp); + // ⚠️ latitude & longitude sengaja TIDAK dikirim + + showLoading(); + + try { + const res = await fetch('edit.php', { method: 'POST', body: formData }); + const json = await res.json(); + + if (json.status === 'success') { + const spbuBaru = json.data; + + // Perbarui ikon marker sesuai kondisi baru + const warnaBar = getWarnaMarker(spbuBaru); + markersMap[id].setIcon(buatIkon(warnaBar)); + + // Perbarui isi popup dengan data terbaru + markersMap[id].setPopupContent(buatPopupDetail(spbuBaru)); + markersMap[id].openPopup(); + + tampilkanToast('✅ Data berhasil diperbarui!', 'success'); + } else { + tampilkanToast('❌ ' + json.message, 'error'); + } + } catch (err) { + tampilkanToast('❌ Gagal terhubung ke server.', 'error'); + console.error(err); + } finally { + hideLoading(); + } +} + +/* ══════════════════════════════════════════════════════════ + 11. HAPUS DATA SPBU + ══════════════════════════════════════════════════════════ */ + +/** + * Hapus data SPBU dari database dan marker dari peta. + * @param {number} id - ID SPBU yang dihapus + * @param {string} namaSpbu - Nama SPBU untuk konfirmasi + */ +async function hapusData(id, namaSpbu) { + // Konfirmasi sebelum menghapus + if (!confirm(`Yakin ingin menghapus SPBU "${namaSpbu}"?\nTindakan ini tidak dapat dibatalkan.`)) { + return; + } + + const formData = new FormData(); + formData.append('id', id); + + showLoading(); + + try { + const res = await fetch('hapus.php', { method: 'POST', body: formData }); + const json = await res.json(); + + if (json.status === 'success') { + // Hapus marker dari peta + if (markersMap[id]) { + map.removeLayer(markersMap[id]); + delete markersMap[id]; + } + tampilkanToast('🗑️ Data SPBU berhasil dihapus.', 'success'); + } else { + tampilkanToast('❌ ' + json.message, 'error'); + } + } catch (err) { + tampilkanToast('❌ Gagal terhubung ke server.', 'error'); + console.error(err); + } finally { + hideLoading(); + } +} + +/* ══════════════════════════════════════════════════════════ + 12. HELPER: Tampilkan detail dari ID (tombol Batal di form edit) + ══════════════════════════════════════════════════════════ */ + +async function tampilkanDetailDariId(id) { + try { + const res = await fetch('ambil_data.php'); + const json = await res.json(); + const spbu = json.data.find(s => s.id == id); + if (spbu && markersMap[id]) { + markersMap[id].setPopupContent(buatPopupDetail(spbu)).openPopup(); + } + } catch (err) { console.error(err); } +} + +/* ══════════════════════════════════════════════════════════ + 13. INISIALISASI: Muat semua marker saat halaman dibuka + ══════════════════════════════════════════════════════════ */ + +// Panggil fungsi ini sekali saat halaman selesai dimuat +muatSemuaMarker(); diff --git a/01/edit.php b/01/edit.php new file mode 100644 index 0000000..078c40f --- /dev/null +++ b/01/edit.php @@ -0,0 +1,105 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil & bersihkan data dari form ────────────────────────────────────────── +$id = intval($_POST['id'] ?? 0); +$nama_spbu = trim($_POST['nama_spbu'] ?? ''); +$status_ramai = trim($_POST['status_ramai'] ?? ''); +$buka_24_jam = trim($_POST['buka_24_jam'] ?? ''); +$status_operasional = trim($_POST['status_operasional'] ?? ''); +$nomor_hp = trim($_POST['nomor_hp'] ?? ''); + +// ── Validasi: ID harus ada dan valid ───────────────────────────────────────── +if ($id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'ID tidak valid.']); + exit; +} + +// ── Validasi: pastikan tidak ada field yang kosong ──────────────────────────── +if ( + empty($nama_spbu) || + empty($status_ramai) || + empty($buka_24_jam) || + empty($status_operasional) || + empty($nomor_hp) +) { + echo json_encode(['status' => 'error', 'message' => 'Semua field wajib diisi.']); + exit; +} + +// ── Validasi nilai ENUM ─────────────────────────────────────────────────────── +$valid_ramai = ['ramai', 'tidak_ramai']; +$valid_24jam = ['ya', 'tidak']; +$valid_operasional = ['buka', 'tutup']; + +if (!in_array($status_ramai, $valid_ramai)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status ramai tidak valid.']); + exit; +} +if (!in_array($buka_24_jam, $valid_24jam)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai buka 24 jam tidak valid.']); + exit; +} +if (!in_array($status_operasional, $valid_operasional)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status operasional tidak valid.']); + exit; +} + +// ── Update data di database (latitude & longitude TIDAK diubah) ─────────────── +$stmt = $koneksi->prepare( + "UPDATE spbu + SET nama_spbu = ?, status_ramai = ?, buka_24_jam = ?, + status_operasional = ?, nomor_hp = ? + WHERE id = ?" +); + +// Bind parameter: s = string, i = integer +$stmt->bind_param( + 'sssssi', + $nama_spbu, + $status_ramai, + $buka_24_jam, + $status_operasional, + $nomor_hp, + $id +); + +// Eksekusi query +if ($stmt->execute()) { + if ($stmt->affected_rows > 0) { + // Ambil data terbaru setelah di-update untuk dikembalikan ke JavaScript + $result = $koneksi->query("SELECT * FROM spbu WHERE id = $id"); + $updatedData = $result->fetch_assoc(); + $updatedData['latitude'] = (float) $updatedData['latitude']; + $updatedData['longitude'] = (float) $updatedData['longitude']; + + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil diperbarui.', + 'data' => $updatedData + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan atau tidak ada perubahan.']); + } +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal memperbarui data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/01/hapus.php b/01/hapus.php new file mode 100644 index 0000000..7d56ba1 --- /dev/null +++ b/01/hapus.php @@ -0,0 +1,48 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil ID dari data POST ─────────────────────────────────────────────────── +$id = intval($_POST['id'] ?? 0); + +// ── Validasi: ID harus valid ────────────────────────────────────────────────── +if ($id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'ID tidak valid.']); + exit; +} + +// ── Hapus data dari database menggunakan prepared statement ─────────────────── +$stmt = $koneksi->prepare("DELETE FROM spbu WHERE id = ?"); +$stmt->bind_param('i', $id); + +// Eksekusi query +if ($stmt->execute()) { + if ($stmt->affected_rows > 0) { + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil dihapus.', + 'id' => $id + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan.']); + } +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menghapus data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/01/index.php b/01/index.php new file mode 100644 index 0000000..a91a770 --- /dev/null +++ b/01/index.php @@ -0,0 +1,422 @@ + + + + + + WebGIS SPBU + + + + + + + + + + +
+
+
+ + + + + + + + diff --git a/01/koneksi.php b/01/koneksi.php new file mode 100644 index 0000000..2492d41 --- /dev/null +++ b/01/koneksi.php @@ -0,0 +1,21 @@ +set_charset('utf8mb4'); + +if ($koneksi->connect_error) { + http_response_code(500); + die(json_encode([ + 'status' => 'error', + 'message' => 'Koneksi database gagal: ' . $koneksi->connect_error + ])); +} +?> diff --git a/01/tambah.php b/01/tambah.php new file mode 100644 index 0000000..af13d05 --- /dev/null +++ b/01/tambah.php @@ -0,0 +1,103 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil & bersihkan data dari form ────────────────────────────────────────── +$nama_spbu = trim($_POST['nama_spbu'] ?? ''); +$status_ramai = trim($_POST['status_ramai'] ?? ''); +$buka_24_jam = trim($_POST['buka_24_jam'] ?? ''); +$status_operasional = trim($_POST['status_operasional'] ?? ''); +$nomor_hp = trim($_POST['nomor_hp'] ?? ''); +$latitude = trim($_POST['latitude'] ?? ''); +$longitude = trim($_POST['longitude'] ?? ''); + +// ── Validasi: pastikan tidak ada field yang kosong ──────────────────────────── +if ( + empty($nama_spbu) || + empty($status_ramai) || + empty($buka_24_jam) || + empty($status_operasional) || + empty($nomor_hp) || + $latitude === '' || + $longitude === '' +) { + echo json_encode(['status' => 'error', 'message' => 'Semua field wajib diisi.']); + exit; +} + +// ── Validasi nilai ENUM ─────────────────────────────────────────────────────── +$valid_ramai = ['ramai', 'tidak_ramai']; +$valid_24jam = ['ya', 'tidak']; +$valid_operasional = ['buka', 'tutup']; + +if (!in_array($status_ramai, $valid_ramai)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status ramai tidak valid.']); + exit; +} +if (!in_array($buka_24_jam, $valid_24jam)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai buka 24 jam tidak valid.']); + exit; +} +if (!in_array($status_operasional, $valid_operasional)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status operasional tidak valid.']); + exit; +} + +// ── Simpan ke database menggunakan prepared statement (mencegah SQL Injection) ─ +$stmt = $koneksi->prepare( + "INSERT INTO spbu + (nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude) + VALUES (?, ?, ?, ?, ?, ?, ?)" +); + +// Bind parameter: s = string, d = double/float +$stmt->bind_param( + 'sssssdd', + $nama_spbu, + $status_ramai, + $buka_24_jam, + $status_operasional, + $nomor_hp, + $latitude, + $longitude +); + +// Eksekusi query +if ($stmt->execute()) { + // Berhasil: kembalikan ID baru dan semua data yang disimpan + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil ditambahkan.', + 'data' => [ + 'id' => $koneksi->insert_id, + 'nama_spbu' => $nama_spbu, + 'status_ramai' => $status_ramai, + 'buka_24_jam' => $buka_24_jam, + 'status_operasional' => $status_operasional, + 'nomor_hp' => $nomor_hp, + 'latitude' => (float) $latitude, + 'longitude' => (float) $longitude, + ] + ]); +} else { + // Gagal: kembalikan pesan error + echo json_encode(['status' => 'error', 'message' => 'Gagal menyimpan data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/02/02_spbudb.sql b/02/02_spbudb.sql new file mode 100644 index 0000000..b813c28 --- /dev/null +++ b/02/02_spbudb.sql @@ -0,0 +1,143 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `02_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `polygon` +-- + +CREATE TABLE `polygon` ( + `id` int NOT NULL, + `nama_polygon` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `warna` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#22c55e', + `koordinat` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `polygon` +-- + +INSERT INTO `polygon` (`id`, `nama_polygon`, `keterangan`, `warna`, `koordinat`, `created_at`, `updated_at`) VALUES +(1, 'Rumah A', 'Rumah A di tepi jalan imbon', '#c42170', '[[-0.04619857363388995,109.35349255800249],[-0.04609122197957324,109.35370445251466],[-0.046228095338798736,109.35379564762117],[-0.04634349836696752,109.35357302427295]]', '2026-06-08 09:59:46', '2026-06-08 09:59:46'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `polyline` +-- + +CREATE TABLE `polyline` ( + `id` int NOT NULL, + `nama_line` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `warna` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#3b82f6', + `koordinat` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `polyline` +-- + +INSERT INTO `polyline` (`id`, `nama_line`, `keterangan`, `warna`, `koordinat`, `created_at`, `updated_at`) VALUES +(1, 'Jl Tanjung Sari', 'Jalan tembusan \'TanSar\", dari mulut di ImBon dan dari mulut Jl. Jend. Ayani', '#3b82f6', '[[-0.052839376622127024,109.34751927852632],[-0.05137402667155636,109.34858143329622],[-0.050509845915652725,109.34916079044343],[-0.04923236129902831,109.35004591941835],[-0.04847016458323742,109.35060918331146],[-0.04772943818927965,109.35115635395051],[-0.04757377829389879,109.35129046440126],[-0.047589881041730155,109.35139775276186],[-0.04771333544149917,109.35161232948305],[-0.04650562934709907,109.3523955345154],[-0.045646816111789314,109.35296952724458],[-0.0457756380977098,109.35312509536745]]', '2026-06-08 10:03:20', '2026-06-08 10:03:20'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'tidak_ramai', + `buka_24_jam` enum('ya','tidak') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'tidak', + `status_operasional` enum('buka','tutup') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'buka', + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(4, 'SPBU Imbon', 'ramai', 'ya', 'buka', '0812345678913', -0.046981865484009226, 109.35455471277238, '2026-06-08 09:58:55', '2026-06-08 09:58:55'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `polygon` +-- +ALTER TABLE `polygon` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `polyline` +-- +ALTER TABLE `polyline` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `polygon` +-- +ALTER TABLE `polygon` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `polyline` +-- +ALTER TABLE `polyline` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/02/ambil_data.php b/02/ambil_data.php new file mode 100644 index 0000000..f086222 --- /dev/null +++ b/02/ambil_data.php @@ -0,0 +1,69 @@ + 'success', + 'spbu' => [], + 'polyline' => [], + 'polygon' => [], +]; + +// ─── Ambil data SPBU ───────────────────────────────────────────────────────── +$query_spbu = "SELECT id, nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude FROM spbu ORDER BY id ASC"; +$res = $koneksi->query($query_spbu); +if ($res) { + while ($row = $res->fetch_assoc()) { + // Cast tipe data numerik + $row['id'] = (int)$row['id']; + $row['latitude'] = (float)$row['latitude']; + $row['longitude'] = (float)$row['longitude']; + $result['spbu'][] = $row; + } + $res->free(); +} else { + $result['status'] = 'partial_error'; + $result['error_spbu'] = $koneksi->error; +} + +// ─── Ambil data Polyline ────────────────────────────────────────────────────── +$query_poly = "SELECT id, nama_line, keterangan, warna, koordinat FROM polyline ORDER BY id ASC"; +$res = $koneksi->query($query_poly); +if ($res) { + while ($row = $res->fetch_assoc()) { + $row['id'] = (int)$row['id']; + // Decode koordinat JSON → array, kembalikan sebagai array PHP (akan di-encode ulang ke JSON) + $row['koordinat'] = json_decode($row['koordinat'], true); + $result['polyline'][] = $row; + } + $res->free(); +} else { + $result['status'] = 'partial_error'; + $result['error_polyline'] = $koneksi->error; +} + +// ─── Ambil data Polygon ─────────────────────────────────────────────────────── +$query_pgon = "SELECT id, nama_polygon, keterangan, warna, koordinat FROM polygon ORDER BY id ASC"; +$res = $koneksi->query($query_pgon); +if ($res) { + while ($row = $res->fetch_assoc()) { + $row['id'] = (int)$row['id']; + $row['koordinat'] = json_decode($row['koordinat'], true); + $result['polygon'][] = $row; + } + $res->free(); +} else { + $result['status'] = 'partial_error'; + $result['error_polygon'] = $koneksi->error; +} + +$koneksi->close(); + +echo json_encode($result); diff --git a/02/edit.php b/02/edit.php new file mode 100644 index 0000000..7a7e13a --- /dev/null +++ b/02/edit.php @@ -0,0 +1,82 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +require_once 'koneksi.php'; + +$tipe = isset($_POST['tipe']) ? trim($_POST['tipe']) : ''; + +// Saat ini hanya menangani edit SPBU +if ($tipe === 'spbu') { + $id = isset($_POST['id']) ? intval($_POST['id']) : 0; + $nama_spbu = isset($_POST['nama_spbu']) ? trim($_POST['nama_spbu']) : ''; + $status_ramai = isset($_POST['status_ramai']) ? trim($_POST['status_ramai']) : ''; + $buka_24_jam = isset($_POST['buka_24_jam']) ? trim($_POST['buka_24_jam']) : ''; + $status_operasional = isset($_POST['status_operasional']) ? trim($_POST['status_operasional']) : ''; + $nomor_hp = isset($_POST['nomor_hp']) ? trim($_POST['nomor_hp']) : ''; + + // Validasi + if ($id <= 0 || empty($nama_spbu) || empty($status_ramai) || empty($buka_24_jam) || empty($status_operasional)) { + echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap atau ID tidak valid.']); + exit; + } + + $valid_ramai = ['ramai', 'tidak_ramai']; + $valid_24 = ['ya', 'tidak']; + $valid_ops = ['buka', 'tutup']; + + if (!in_array($status_ramai, $valid_ramai) || !in_array($buka_24_jam, $valid_24) || !in_array($status_operasional, $valid_ops)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai enum tidak valid.']); + exit; + } + + // Update — latitude & longitude sengaja tidak diubah + $stmt = $koneksi->prepare( + "UPDATE spbu SET nama_spbu=?, status_ramai=?, buka_24_jam=?, status_operasional=?, nomor_hp=? + WHERE id=?" + ); + $stmt->bind_param('sssssi', $nama_spbu, $status_ramai, $buka_24_jam, $status_operasional, $nomor_hp, $id); + + if ($stmt->execute()) { + if ($stmt->affected_rows >= 0) { + // Ambil data lengkap (termasuk lat/lng) untuk dikembalikan ke JS + $sel = $koneksi->prepare("SELECT id, nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude FROM spbu WHERE id=?"); + $sel->bind_param('i', $id); + $sel->execute(); + $res = $sel->get_result(); + $data = $res->fetch_assoc(); + $sel->close(); + + $data['id'] = (int)$data['id']; + $data['latitude'] = (float)$data['latitude']; + $data['longitude'] = (float)$data['longitude']; + + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil diupdate.', + 'data' => $data + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan.']); + } + } else { + echo json_encode(['status' => 'error', 'message' => 'Gagal update: ' . $stmt->error]); + } + $stmt->close(); + +} else { + echo json_encode(['status' => 'error', 'message' => 'Tipe tidak dikenali untuk edit.']); +} + +$koneksi->close(); diff --git a/02/hapus.php b/02/hapus.php new file mode 100644 index 0000000..7dff21b --- /dev/null +++ b/02/hapus.php @@ -0,0 +1,60 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +require_once 'koneksi.php'; + +$tipe = isset($_POST['tipe']) ? trim($_POST['tipe']) : ''; +$id = isset($_POST['id']) ? intval($_POST['id']) : 0; + +if ($id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'ID tidak valid.']); + exit; +} + +// Mapping tipe ke nama tabel +$tabel_map = [ + 'spbu' => 'spbu', + 'polyline' => 'polyline', + 'polygon' => 'polygon', +]; + +if (!array_key_exists($tipe, $tabel_map)) { + echo json_encode(['status' => 'error', 'message' => 'Tipe tidak dikenali.']); + exit; +} + +$tabel = $tabel_map[$tipe]; + +// Prepared statement hapus berdasarkan tipe dan id +$stmt = $koneksi->prepare("DELETE FROM `{$tabel}` WHERE id = ?"); +$stmt->bind_param('i', $id); + +if ($stmt->execute()) { + if ($stmt->affected_rows > 0) { + echo json_encode([ + 'status' => 'success', + 'message' => ucfirst($tipe) . ' berhasil dihapus.', + 'id' => $id, + 'tipe' => $tipe + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan.']); + } +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menghapus: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); diff --git a/02/index.php b/02/index.php new file mode 100644 index 0000000..1ca91f3 --- /dev/null +++ b/02/index.php @@ -0,0 +1,1136 @@ + + + + + + WebGIS Manajemen SPBU + + + + + + + + + + + +
+ + +
+
Klik peta → Tambah SPBU
+
Toolbar kiri → Gambar Polyline / Polygon
+
+ +
+
Tenang+24j
+
Tenang
+
Ramai+24j
+
Ramai
+
Tutup
+
+
+ + +
+ + +
+ + +
+ + + + + + + + + diff --git a/02/koneksi.php b/02/koneksi.php new file mode 100644 index 0000000..add552f --- /dev/null +++ b/02/koneksi.php @@ -0,0 +1,23 @@ +connect_error) { + http_response_code(500); + header('Content-Type: application/json'); + echo json_encode([ + 'status' => 'error', + 'message' => 'Koneksi database gagal: ' . $koneksi->connect_error + ]); + exit; +} + +$koneksi->set_charset('utf8mb4'); +?> diff --git a/02/setup_database.sql b/02/setup_database.sql new file mode 100644 index 0000000..fe047b9 --- /dev/null +++ b/02/setup_database.sql @@ -0,0 +1,70 @@ +-- ═══════════════════════════════════════════════════════════════════ +-- setup_database.sql +-- Jalankan query ini di phpMyAdmin (Laragon) atau MySQL CLI +-- untuk membuat database dan tabel yang diperlukan. +-- ═══════════════════════════════════════════════════════════════════ + +-- 1. Buat dan gunakan database +CREATE DATABASE IF NOT EXISTS `02_spbudb` + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +USE `02_spbudb`; + +-- ───────────────────────────────────────────────────────────────────── +-- 2. Tabel SPBU (marker titik lokasi) +-- ───────────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS `spbu` ( + `id` INT NOT NULL AUTO_INCREMENT, + `nama_spbu` VARCHAR(100) NOT NULL, + `status_ramai` ENUM('ramai','tidak_ramai') NOT NULL DEFAULT 'tidak_ramai', + `buka_24_jam` ENUM('ya','tidak') NOT NULL DEFAULT 'tidak', + `status_operasional` ENUM('buka','tutup') NOT NULL DEFAULT 'buka', + `nomor_hp` VARCHAR(20) NOT NULL DEFAULT '', + `latitude` DOUBLE NOT NULL, + `longitude` DOUBLE NOT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ───────────────────────────────────────────────────────────────────── +-- 3. Tabel Polyline (garis) +-- Kolom koordinat menyimpan JSON array of [lat, lng] +-- Contoh: [[-0.055, 109.348], [-0.056, 109.349]] +-- ───────────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS `polyline` ( + `id` INT NOT NULL AUTO_INCREMENT, + `nama_line` VARCHAR(100) NOT NULL, + `keterangan` TEXT, + `warna` VARCHAR(20) NOT NULL DEFAULT '#3b82f6', + `koordinat` LONGTEXT NOT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ───────────────────────────────────────────────────────────────────── +-- 4. Tabel Polygon (area) +-- Kolom koordinat menyimpan JSON array of [lat, lng] +-- Contoh: [[-0.055, 109.348], [-0.056, 109.349], [-0.055, 109.350]] +-- ───────────────────────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS `polygon` ( + `id` INT NOT NULL AUTO_INCREMENT, + `nama_polygon` VARCHAR(100) NOT NULL, + `keterangan` TEXT, + `warna` VARCHAR(20) NOT NULL DEFAULT '#22c55e', + `koordinat` LONGTEXT NOT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ───────────────────────────────────────────────────────────────────── +-- 5. (Opsional) Data contoh untuk testing awal +-- ───────────────────────────────────────────────────────────────────── +INSERT INTO `spbu` (nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude) +VALUES + ('SPBU Contoh A', 'ramai', 'ya', 'buka', '08123456789', -0.05566, 109.34854), + ('SPBU Contoh B', 'tidak_ramai', 'tidak', 'buka', '08129876543', -0.05700, 109.34900), + ('SPBU Contoh C', 'ramai', 'tidak', 'tutup', '', -0.05400, 109.34800); diff --git a/02/tambah.php b/02/tambah.php new file mode 100644 index 0000000..ccdd566 --- /dev/null +++ b/02/tambah.php @@ -0,0 +1,163 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +require_once 'koneksi.php'; + +$tipe = isset($_POST['tipe']) ? trim($_POST['tipe']) : ''; + +// ─── TAMBAH SPBU ──────────────────────────────────────────────────────────── +if ($tipe === 'spbu') { + $nama_spbu = isset($_POST['nama_spbu']) ? trim($_POST['nama_spbu']) : ''; + $status_ramai = isset($_POST['status_ramai']) ? trim($_POST['status_ramai']) : ''; + $buka_24_jam = isset($_POST['buka_24_jam']) ? trim($_POST['buka_24_jam']) : ''; + $status_operasional = isset($_POST['status_operasional']) ? trim($_POST['status_operasional']) : ''; + $nomor_hp = isset($_POST['nomor_hp']) ? trim($_POST['nomor_hp']) : ''; + $latitude = isset($_POST['latitude']) ? floatval($_POST['latitude']) : null; + $longitude = isset($_POST['longitude']) ? floatval($_POST['longitude']) : null; + + // Validasi server-side + if (empty($nama_spbu) || empty($status_ramai) || empty($buka_24_jam) || empty($status_operasional) || $latitude === null || $longitude === null) { + echo json_encode(['status' => 'error', 'message' => 'Data tidak lengkap.']); + exit; + } + + $valid_ramai = ['ramai', 'tidak_ramai']; + $valid_24 = ['ya', 'tidak']; + $valid_ops = ['buka', 'tutup']; + + if (!in_array($status_ramai, $valid_ramai) || !in_array($buka_24_jam, $valid_24) || !in_array($status_operasional, $valid_ops)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai enum tidak valid.']); + exit; + } + + $stmt = $koneksi->prepare( + "INSERT INTO spbu (nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude) + VALUES (?, ?, ?, ?, ?, ?, ?)" + ); + $stmt->bind_param('sssssdd', $nama_spbu, $status_ramai, $buka_24_jam, $status_operasional, $nomor_hp, $latitude, $longitude); + + if ($stmt->execute()) { + $id = $koneksi->insert_id; + echo json_encode([ + 'status' => 'success', + 'message' => 'SPBU berhasil ditambahkan.', + 'id' => $id, + 'data' => [ + 'id' => $id, + 'nama_spbu' => $nama_spbu, + 'status_ramai' => $status_ramai, + 'buka_24_jam' => $buka_24_jam, + 'status_operasional' => $status_operasional, + 'nomor_hp' => $nomor_hp, + 'latitude' => $latitude, + 'longitude' => $longitude, + ] + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menyimpan SPBU: ' . $stmt->error]); + } + $stmt->close(); + +// ─── TAMBAH POLYLINE ───────────────────────────────────────────────────────── +} elseif ($tipe === 'polyline') { + $nama_line = isset($_POST['nama_line']) ? trim($_POST['nama_line']) : ''; + $keterangan = isset($_POST['keterangan']) ? trim($_POST['keterangan']) : ''; + $warna = isset($_POST['warna']) ? trim($_POST['warna']) : '#3b82f6'; + $koordinat = isset($_POST['koordinat']) ? trim($_POST['koordinat']) : ''; + + if (empty($nama_line) || empty($koordinat)) { + echo json_encode(['status' => 'error', 'message' => 'Nama line dan koordinat wajib diisi.']); + exit; + } + + // Validasi JSON koordinat + $decoded = json_decode($koordinat, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) { + echo json_encode(['status' => 'error', 'message' => 'Format koordinat tidak valid.']); + exit; + } + + $stmt = $koneksi->prepare( + "INSERT INTO polyline (nama_line, keterangan, warna, koordinat) VALUES (?, ?, ?, ?)" + ); + $stmt->bind_param('ssss', $nama_line, $keterangan, $warna, $koordinat); + + if ($stmt->execute()) { + $id = $koneksi->insert_id; + echo json_encode([ + 'status' => 'success', + 'message' => 'Polyline berhasil ditambahkan.', + 'id' => $id, + 'data' => [ + 'id' => $id, + 'nama_line' => $nama_line, + 'keterangan' => $keterangan, + 'warna' => $warna, + 'koordinat' => $koordinat, + ] + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menyimpan polyline: ' . $stmt->error]); + } + $stmt->close(); + +// ─── TAMBAH POLYGON ────────────────────────────────────────────────────────── +} elseif ($tipe === 'polygon') { + $nama_polygon = isset($_POST['nama_polygon']) ? trim($_POST['nama_polygon']) : ''; + $keterangan = isset($_POST['keterangan']) ? trim($_POST['keterangan']) : ''; + $warna = isset($_POST['warna']) ? trim($_POST['warna']) : '#22c55e'; + $koordinat = isset($_POST['koordinat']) ? trim($_POST['koordinat']) : ''; + + if (empty($nama_polygon) || empty($koordinat)) { + echo json_encode(['status' => 'error', 'message' => 'Nama polygon dan koordinat wajib diisi.']); + exit; + } + + $decoded = json_decode($koordinat, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) { + echo json_encode(['status' => 'error', 'message' => 'Format koordinat tidak valid.']); + exit; + } + + $stmt = $koneksi->prepare( + "INSERT INTO polygon (nama_polygon, keterangan, warna, koordinat) VALUES (?, ?, ?, ?)" + ); + $stmt->bind_param('ssss', $nama_polygon, $keterangan, $warna, $koordinat); + + if ($stmt->execute()) { + $id = $koneksi->insert_id; + echo json_encode([ + 'status' => 'success', + 'message' => 'Polygon berhasil ditambahkan.', + 'id' => $id, + 'data' => [ + 'id' => $id, + 'nama_polygon' => $nama_polygon, + 'keterangan' => $keterangan, + 'warna' => $warna, + 'koordinat' => $koordinat, + ] + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menyimpan polygon: ' . $stmt->error]); + } + $stmt->close(); + +} else { + echo json_encode(['status' => 'error', 'message' => 'Tipe tidak dikenali.']); +} + +$koneksi->close(); diff --git a/03/03_webgisdb.sql b/03/03_webgisdb.sql new file mode 100644 index 0000000..d2c0917 --- /dev/null +++ b/03/03_webgisdb.sql @@ -0,0 +1,106 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `03_webgisdb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `jalan` +-- + +CREATE TABLE `jalan` ( + `id` int NOT NULL, + `nama_jalan` varchar(150) NOT NULL, + `status_jalan` enum('nasional','provinsi','kabupaten') NOT NULL, + `panjang_meter` double NOT NULL, + `geojson` longtext NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `jalan` +-- + +INSERT INTO `jalan` (`id`, `nama_jalan`, `status_jalan`, `panjang_meter`, `geojson`, `created_at`, `updated_at`) VALUES +(1, 'Gg. Pak Cik', 'kabupaten', 169.77, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[109.34135,-0.055498],[109.342616,-0.056351]]}}', '2026-06-08 12:30:39', '2026-06-08 12:30:39'), +(2, 'Jl. Trans Kalimantan', 'nasional', 5544.23, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[109.374583,-0.069933],[109.375999,-0.068258],[109.379303,-0.064651],[109.380591,-0.063685],[109.381771,-0.062891],[109.383423,-0.061517],[109.385548,-0.059992],[109.386578,-0.059327],[109.387114,-0.059048],[109.387758,-0.058833],[109.38868,-0.058318],[109.389496,-0.058038],[109.390697,-0.05748],[109.391813,-0.056815],[109.393015,-0.056128],[109.394302,-0.055677],[109.39559,-0.055226],[109.396598,-0.054882],[109.397478,-0.054603],[109.398851,-0.054324],[109.401062,-0.053723],[109.401855,-0.053465],[109.402649,-0.052907],[109.403057,-0.052284],[109.403958,-0.049257],[109.405246,-0.044942],[109.406469,-0.041313],[109.406598,-0.04054],[109.406598,-0.039338],[109.406254,-0.038694],[109.406254,-0.038221],[109.406641,-0.038178],[109.406984,-0.038565]]}}', '2026-06-08 12:34:53', '2026-06-08 12:34:53'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `parsil_tanah` +-- + +CREATE TABLE `parsil_tanah` ( + `id` int NOT NULL, + `nama_parsil` varchar(150) NOT NULL, + `status_kepemilikan` enum('SHM','HGB','HGU','HP') NOT NULL, + `luas_meter` double NOT NULL, + `geojson` longtext NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `parsil_tanah` +-- + +INSERT INTO `parsil_tanah` (`id`, `nama_parsil`, `status_kepemilikan`, `luas_meter`, `geojson`, `created_at`, `updated_at`) VALUES +(1, 'kavling A1', 'HP', 1239.31, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[109.339339,-0.060625],[109.339548,-0.060442],[109.339803,-0.060689],[109.339575,-0.060877],[109.339339,-0.060625]]]}}', '2026-06-08 12:30:03', '2026-06-08 12:30:03'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `jalan` +-- +ALTER TABLE `jalan` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `parsil_tanah` +-- +ALTER TABLE `parsil_tanah` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `jalan` +-- +ALTER TABLE `jalan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + +-- +-- AUTO_INCREMENT for table `parsil_tanah` +-- +ALTER TABLE `parsil_tanah` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/03/ambil_data.php b/03/ambil_data.php new file mode 100644 index 0000000..060c56f --- /dev/null +++ b/03/ambil_data.php @@ -0,0 +1,60 @@ + false, + "message" => "Method tidak diizinkan." + ], 405); +} + +$jalan = []; +$parsil = []; + +// Ambil data jalan +$queryJalan = "SELECT id, nama_jalan, status_jalan, panjang_meter, geojson, created_at, updated_at + FROM jalan + ORDER BY id ASC"; +$resultJalan = $koneksi->query($queryJalan); + +if (!$resultJalan) { + json_response([ + "success" => false, + "message" => "Gagal mengambil data jalan: " . $koneksi->error + ], 500); +} + +while ($row = $resultJalan->fetch_assoc()) { + $row["id"] = (int) $row["id"]; + $row["panjang_meter"] = (float) $row["panjang_meter"]; + $jalan[] = $row; +} + +// Ambil data parsil tanah +$queryParsil = "SELECT id, nama_parsil, status_kepemilikan, luas_meter, geojson, created_at, updated_at + FROM parsil_tanah + ORDER BY id ASC"; +$resultParsil = $koneksi->query($queryParsil); + +if (!$resultParsil) { + json_response([ + "success" => false, + "message" => "Gagal mengambil data parsil tanah: " . $koneksi->error + ], 500); +} + +while ($row = $resultParsil->fetch_assoc()) { + $row["id"] = (int) $row["id"]; + $row["luas_meter"] = (float) $row["luas_meter"]; + $parsil[] = $row; +} + +json_response([ + "success" => true, + "jalan" => $jalan, + "parsil" => $parsil +]); +?> diff --git a/03/database.sql b/03/database.sql new file mode 100644 index 0000000..b014281 --- /dev/null +++ b/03/database.sql @@ -0,0 +1,26 @@ +-- Database dan tabel aplikasi WebGIS +CREATE DATABASE IF NOT EXISTS `03_webgisdb` +CHARACTER SET utf8mb4 +COLLATE utf8mb4_general_ci; + +USE `03_webgisdb`; + +CREATE TABLE IF NOT EXISTS `jalan` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `nama_jalan` VARCHAR(150) NOT NULL, + `status_jalan` ENUM('nasional','provinsi','kabupaten') NOT NULL, + `panjang_meter` DOUBLE NOT NULL, + `geojson` LONGTEXT NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `parsil_tanah` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `nama_parsil` VARCHAR(150) NOT NULL, + `status_kepemilikan` ENUM('SHM','HGB','HGU','HP') NOT NULL, + `luas_meter` DOUBLE NOT NULL, + `geojson` LONGTEXT NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/03/edit.php b/03/edit.php new file mode 100644 index 0000000..3969cf6 --- /dev/null +++ b/03/edit.php @@ -0,0 +1,129 @@ + false, + "message" => "Edit data hanya menerima method POST." + ], 405); +} + +$tipe = $_POST["tipe"] ?? ""; +$id = $_POST["id"] ?? ""; + +if (!is_numeric($id) || (int) $id <= 0) { + json_response([ + "success" => false, + "message" => "ID tidak valid." + ], 422); +} + +$id = (int) $id; + +if ($tipe === "jalan") { + $nama_jalan = trim($_POST["nama_jalan"] ?? ""); + $status_jalan = $_POST["status_jalan"] ?? ""; + $status_valid = ["nasional", "provinsi", "kabupaten"]; + + if ($nama_jalan === "") { + json_response(["success" => false, "message" => "Nama jalan wajib diisi."], 422); + } + + if (!in_array($status_jalan, $status_valid, true)) { + json_response(["success" => false, "message" => "Status jalan tidak valid."], 422); + } + + $stmt = $koneksi->prepare( + "UPDATE jalan + SET nama_jalan = ?, status_jalan = ? + WHERE id = ?" + ); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("ssi", $nama_jalan, $status_jalan, $id); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal mengedit data jalan: " . $stmt->error], 500); + } + + if ($stmt->affected_rows === 0) { + // Bisa saja data sama persis, jadi tetap cek apakah ID ada. + $cek = $koneksi->prepare("SELECT id FROM jalan WHERE id = ?"); + $cek->bind_param("i", $id); + $cek->execute(); + $hasil = $cek->get_result(); + if ($hasil->num_rows === 0) { + json_response(["success" => false, "message" => "Data jalan tidak ditemukan."], 404); + } + $cek->close(); + } + + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data jalan berhasil diedit." + ]); +} + +if ($tipe === "parsil") { + $nama_parsil = trim($_POST["nama_parsil"] ?? ""); + $status_kepemilikan = $_POST["status_kepemilikan"] ?? ""; + $status_valid = ["SHM", "HGB", "HGU", "HP"]; + + if ($nama_parsil === "") { + json_response(["success" => false, "message" => "Nama parsil wajib diisi."], 422); + } + + if (!in_array($status_kepemilikan, $status_valid, true)) { + json_response(["success" => false, "message" => "Status kepemilikan tidak valid."], 422); + } + + $stmt = $koneksi->prepare( + "UPDATE parsil_tanah + SET nama_parsil = ?, status_kepemilikan = ? + WHERE id = ?" + ); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("ssi", $nama_parsil, $status_kepemilikan, $id); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal mengedit data parsil tanah: " . $stmt->error], 500); + } + + if ($stmt->affected_rows === 0) { + // Bisa saja data sama persis, jadi tetap cek apakah ID ada. + $cek = $koneksi->prepare("SELECT id FROM parsil_tanah WHERE id = ?"); + $cek->bind_param("i", $id); + $cek->execute(); + $hasil = $cek->get_result(); + if ($hasil->num_rows === 0) { + json_response(["success" => false, "message" => "Data parsil tanah tidak ditemukan."], 404); + } + $cek->close(); + } + + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data parsil tanah berhasil diedit." + ]); +} + +json_response([ + "success" => false, + "message" => "Tipe data tidak valid. Gunakan tipe=jalan atau tipe=parsil." +], 422); +?> diff --git a/03/hapus.php b/03/hapus.php new file mode 100644 index 0000000..2f35a1a --- /dev/null +++ b/03/hapus.php @@ -0,0 +1,80 @@ + false, + "message" => "Hapus data hanya menerima method POST." + ], 405); +} + +$tipe = $_POST["tipe"] ?? ""; +$id = $_POST["id"] ?? ""; + +if (!is_numeric($id) || (int) $id <= 0) { + json_response([ + "success" => false, + "message" => "ID tidak valid." + ], 422); +} + +$id = (int) $id; + +if ($tipe === "jalan") { + $stmt = $koneksi->prepare("DELETE FROM jalan WHERE id = ?"); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("i", $id); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal menghapus data jalan: " . $stmt->error], 500); + } + + if ($stmt->affected_rows === 0) { + json_response(["success" => false, "message" => "Data jalan tidak ditemukan."], 404); + } + + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data jalan berhasil dihapus." + ]); +} + +if ($tipe === "parsil") { + $stmt = $koneksi->prepare("DELETE FROM parsil_tanah WHERE id = ?"); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("i", $id); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal menghapus data parsil tanah: " . $stmt->error], 500); + } + + if ($stmt->affected_rows === 0) { + json_response(["success" => false, "message" => "Data parsil tanah tidak ditemukan."], 404); + } + + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data parsil tanah berhasil dihapus." + ]); +} + +json_response([ + "success" => false, + "message" => "Tipe data tidak valid. Gunakan tipe=jalan atau tipe=parsil." +], 422); +?> diff --git a/03/index.php b/03/index.php new file mode 100644 index 0000000..01029d3 --- /dev/null +++ b/03/index.php @@ -0,0 +1,1177 @@ + + + + + WebGIS Manajemen Jalan dan Parsil Tanah + + + + + + + + + + + + + +
+

WebGIS Manajemen Jalan dan Parsil Tanah

+

Gunakan toolbar gambar untuk membuat line/polyline jalan dan polygon parsil tanah.

+
+ +
+ +
+

Legenda

+ +
Status Jalan
+
+ + Jalan Nasional +
+
+ + Jalan Provinsi +
+
+ + Jalan Kabupaten +
+ +
Status Kepemilikan Tanah
+
+ + SHM +
+
+ + HGB +
+
+ + HGU +
+
+ + HP +
+
+ +
+ +
+
+ Memproses data... +
+ + + + diff --git a/03/koneksi.php b/03/koneksi.php new file mode 100644 index 0000000..e798f7e --- /dev/null +++ b/03/koneksi.php @@ -0,0 +1,31 @@ +connect_error) { + header('Content-Type: application/json; charset=utf-8'); + http_response_code(500); + echo json_encode([ + 'success' => false, + 'message' => 'Koneksi database gagal: ' . $koneksi->connect_error + ]); + exit; +} + +$koneksi->set_charset('utf8mb4'); + +function json_response($data, $status_code = 200) +{ + header('Content-Type: application/json; charset=utf-8'); + http_response_code($status_code); + echo json_encode($data); + exit; +} +?> diff --git a/03/tambah.php b/03/tambah.php new file mode 100644 index 0000000..adbff38 --- /dev/null +++ b/03/tambah.php @@ -0,0 +1,145 @@ + false, + "message" => "Tambah data hanya menerima method POST." + ], 405); +} + +function valid_geojson($geojson) +{ + if ($geojson === "") { + return false; + } + + json_decode($geojson, true); + return json_last_error() === JSON_ERROR_NONE; +} + +$tipe = $_POST["tipe"] ?? ""; + +if ($tipe === "jalan") { + $nama_jalan = trim($_POST["nama_jalan"] ?? ""); + $status_jalan = $_POST["status_jalan"] ?? ""; + $panjang_meter = $_POST["panjang_meter"] ?? ""; + $geojson = trim($_POST["geojson"] ?? ""); + + $status_valid = ["nasional", "provinsi", "kabupaten"]; + + if ($nama_jalan === "") { + json_response(["success" => false, "message" => "Nama jalan wajib diisi."], 422); + } + + if (!in_array($status_jalan, $status_valid, true)) { + json_response(["success" => false, "message" => "Status jalan tidak valid."], 422); + } + + if (!is_numeric($panjang_meter) || (float) $panjang_meter <= 0) { + json_response(["success" => false, "message" => "Panjang jalan tidak valid."], 422); + } + + if (!valid_geojson($geojson)) { + json_response(["success" => false, "message" => "GeoJSON jalan tidak valid."], 422); + } + + $panjang_meter = (float) $panjang_meter; + + $stmt = $koneksi->prepare( + "INSERT INTO jalan (nama_jalan, status_jalan, panjang_meter, geojson) + VALUES (?, ?, ?, ?)" + ); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("ssds", $nama_jalan, $status_jalan, $panjang_meter, $geojson); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal menyimpan data jalan: " . $stmt->error], 500); + } + + $id = $stmt->insert_id; + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data jalan berhasil ditambahkan.", + "data" => [ + "id" => (int) $id, + "nama_jalan" => $nama_jalan, + "status_jalan" => $status_jalan, + "panjang_meter" => $panjang_meter, + "geojson" => $geojson + ] + ]); +} + +if ($tipe === "parsil") { + $nama_parsil = trim($_POST["nama_parsil"] ?? ""); + $status_kepemilikan = $_POST["status_kepemilikan"] ?? ""; + $luas_meter = $_POST["luas_meter"] ?? ""; + $geojson = trim($_POST["geojson"] ?? ""); + + $status_valid = ["SHM", "HGB", "HGU", "HP"]; + + if ($nama_parsil === "") { + json_response(["success" => false, "message" => "Nama parsil wajib diisi."], 422); + } + + if (!in_array($status_kepemilikan, $status_valid, true)) { + json_response(["success" => false, "message" => "Status kepemilikan tidak valid."], 422); + } + + if (!is_numeric($luas_meter) || (float) $luas_meter <= 0) { + json_response(["success" => false, "message" => "Luas tanah tidak valid."], 422); + } + + if (!valid_geojson($geojson)) { + json_response(["success" => false, "message" => "GeoJSON parsil tanah tidak valid."], 422); + } + + $luas_meter = (float) $luas_meter; + + $stmt = $koneksi->prepare( + "INSERT INTO parsil_tanah (nama_parsil, status_kepemilikan, luas_meter, geojson) + VALUES (?, ?, ?, ?)" + ); + + if (!$stmt) { + json_response(["success" => false, "message" => "Prepare statement gagal: " . $koneksi->error], 500); + } + + $stmt->bind_param("ssds", $nama_parsil, $status_kepemilikan, $luas_meter, $geojson); + + if (!$stmt->execute()) { + json_response(["success" => false, "message" => "Gagal menyimpan data parsil tanah: " . $stmt->error], 500); + } + + $id = $stmt->insert_id; + $stmt->close(); + + json_response([ + "success" => true, + "message" => "Data parsil tanah berhasil ditambahkan.", + "data" => [ + "id" => (int) $id, + "nama_parsil" => $nama_parsil, + "status_kepemilikan" => $status_kepemilikan, + "luas_meter" => $luas_meter, + "geojson" => $geojson + ] + ]); +} + +json_response([ + "success" => false, + "message" => "Tipe data tidak valid. Gunakan tipe=jalan atau tipe=parsil." +], 422); +?> diff --git a/04/04_spbudb.sql b/04/04_spbudb.sql new file mode 100644 index 0000000..5ed9c2d --- /dev/null +++ b/04/04_spbudb.sql @@ -0,0 +1,77 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `04_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') COLLATE utf8mb4_unicode_ci NOT NULL, + `buka_24_jam` enum('ya','tidak') COLLATE utf8mb4_unicode_ci NOT NULL, + `status_operasional` enum('buka','tutup') COLLATE utf8mb4_unicode_ci NOT NULL, + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(1, 'SPBU Gertak 1', 'tidak_ramai', 'ya', 'buka', '0815497422156', -0.0551481, 109.3480718, '2026-06-09 06:06:31', '2026-06-09 06:06:31'), +(2, 'SPBU Gertak 1', 'ramai', 'ya', 'buka', '0815497422156', -0.0548271, 109.3477392, '2026-06-09 06:06:52', '2026-06-09 06:07:09'), +(3, 'SPBU OSO', 'ramai', 'tidak', 'buka', '0815497422156', -0.0551277, 109.3474925, '2026-06-09 06:07:35', '2026-06-09 06:07:35'), +(4, 'SPBU OSO', 'tidak_ramai', 'tidak', 'buka', '087894587965', -0.0552458, 109.3478036, '2026-06-09 06:07:54', '2026-06-09 06:07:54'), +(5, 'SPBU Gertak 2', 'ramai', 'ya', 'tutup', '0815497422156', -0.0554175, 109.3474495, '2026-06-09 06:08:15', '2026-06-09 06:08:15'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/04/ambil_data.php b/04/ambil_data.php new file mode 100644 index 0000000..7e5051b --- /dev/null +++ b/04/ambil_data.php @@ -0,0 +1,40 @@ +query("SELECT * FROM spbu ORDER BY id DESC"); + +// Cek apakah query berhasil +if (!$result) { + echo json_encode(['status' => 'error', 'message' => 'Gagal mengambil data: ' . $koneksi->error]); + exit; +} + +// ── Kumpulkan semua baris ke dalam array ────────────────────────────────────── +$data = []; +while ($row = $result->fetch_assoc()) { + // Konversi latitude & longitude ke float agar JSON valid + $row['latitude'] = (float) $row['latitude']; + $row['longitude'] = (float) $row['longitude']; + $data[] = $row; +} + +// ── Kembalikan data sebagai JSON ────────────────────────────────────────────── +echo json_encode([ + 'status' => 'success', + 'data' => $data +]); + +$koneksi->close(); +?> diff --git a/04/edit.php b/04/edit.php new file mode 100644 index 0000000..ae501ca --- /dev/null +++ b/04/edit.php @@ -0,0 +1,115 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil & bersihkan input dari form (tanpa latitude/longitude) ────────────── +$id = intval($_POST['id'] ?? 0); +$nama_spbu = trim($_POST['nama_spbu'] ?? ''); +$status_ramai = trim($_POST['status_ramai'] ?? ''); +$buka_24_jam = trim($_POST['buka_24_jam'] ?? ''); +$status_operasional = trim($_POST['status_operasional'] ?? ''); +$nomor_hp = trim($_POST['nomor_hp'] ?? ''); + +// ── Validasi: ID harus valid ────────────────────────────────────────────────── +if ($id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'ID tidak valid.']); + exit; +} + +// ── Validasi: semua field wajib terisi ─────────────────────────────────────── +if ( + empty($nama_spbu) || + empty($status_ramai) || + empty($buka_24_jam) || + empty($status_operasional) || + empty($nomor_hp) +) { + echo json_encode(['status' => 'error', 'message' => 'Semua field wajib diisi.']); + exit; +} + +// ── Validasi nilai ENUM (server-side) ──────────────────────────────────────── +$valid_ramai = ['ramai', 'tidak_ramai']; +$valid_24jam = ['ya', 'tidak']; +$valid_operasional = ['buka', 'tutup']; + +if (!in_array($status_ramai, $valid_ramai)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status ramai tidak valid.']); + exit; +} +if (!in_array($buka_24_jam, $valid_24jam)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai buka 24 jam tidak valid.']); + exit; +} +if (!in_array($status_operasional, $valid_operasional)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status operasional tidak valid.']); + exit; +} + +// ── Update data di database menggunakan prepared statement ─────────────────── +// Latitude dan longitude TIDAK disertakan dalam UPDATE — tidak berubah +$stmt = $koneksi->prepare( + "UPDATE spbu + SET nama_spbu = ?, status_ramai = ?, buka_24_jam = ?, + status_operasional = ?, nomor_hp = ? + WHERE id = ?" +); + +// Bind parameter: s = string, i = integer +$stmt->bind_param( + 'sssssi', + $nama_spbu, + $status_ramai, + $buka_24_jam, + $status_operasional, + $nomor_hp, + $id +); + +// Eksekusi query +if ($stmt->execute()) { + if ($stmt->affected_rows > 0 || $stmt->affected_rows === 0) { + // Ambil data terbaru dari DB untuk dikembalikan ke JavaScript + // JS menggunakan data ini untuk memperbarui marker & layer group + $result = $koneksi->query("SELECT * FROM spbu WHERE id = $id"); + if ($result && $result->num_rows > 0) { + $updatedData = $result->fetch_assoc(); + $updatedData['latitude'] = (float) $updatedData['latitude']; + $updatedData['longitude'] = (float) $updatedData['longitude']; + + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil diperbarui.', + 'data' => $updatedData + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan setelah update.']); + } + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan.']); + } +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal memperbarui data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/04/hapus.php b/04/hapus.php new file mode 100644 index 0000000..46cdbcc --- /dev/null +++ b/04/hapus.php @@ -0,0 +1,53 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil ID dari data POST ─────────────────────────────────────────────────── +$id = intval($_POST['id'] ?? 0); + +// ── Validasi: ID harus valid (lebih dari 0) ─────────────────────────────────── +if ($id <= 0) { + echo json_encode(['status' => 'error', 'message' => 'ID tidak valid.']); + exit; +} + +// ── Hapus data dari database menggunakan prepared statement ────────────────── +$stmt = $koneksi->prepare("DELETE FROM spbu WHERE id = ?"); +$stmt->bind_param('i', $id); + +// Eksekusi query +if ($stmt->execute()) { + if ($stmt->affected_rows > 0) { + // Kembalikan ID yang dihapus agar JS bisa membersihkan marker dari layer group + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil dihapus.', + 'id' => $id + ]); + } else { + echo json_encode(['status' => 'error', 'message' => 'Data tidak ditemukan.']); + } +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menghapus data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/04/index.php b/04/index.php new file mode 100644 index 0000000..20ae5ed --- /dev/null +++ b/04/index.php @@ -0,0 +1,793 @@ + + + + + + WebGIS SPBU — Layer Groups + + + + + + + + + + +
+
+
+ + + + + + + + diff --git a/04/koneksi.php b/04/koneksi.php new file mode 100644 index 0000000..78cf2c5 --- /dev/null +++ b/04/koneksi.php @@ -0,0 +1,20 @@ +set_charset('utf8mb4'); + +if ($koneksi->connect_error) { + http_response_code(500); + die(json_encode([ + 'status' => 'error', + 'message' => 'Koneksi database gagal: ' . $koneksi->connect_error + ])); +} +?> diff --git a/04/schema.sql b/04/schema.sql new file mode 100644 index 0000000..c33e04b --- /dev/null +++ b/04/schema.sql @@ -0,0 +1,33 @@ +-- ============================================================ +-- schema.sql +-- Jalankan query ini di phpMyAdmin atau MySQL CLI Laragon +-- untuk membuat database dan tabel project 04 +-- ============================================================ + +-- Buat database (jika belum ada) +CREATE DATABASE IF NOT EXISTS `04_spbudb` + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +-- Gunakan database yang baru dibuat +USE `04_spbudb`; + +-- Hapus tabel jika sudah ada (untuk reset bersih) +DROP TABLE IF EXISTS `spbu`; + +-- Buat tabel spbu +CREATE TABLE `spbu` ( + `id` INT NOT NULL AUTO_INCREMENT, + `nama_spbu` VARCHAR(100) NOT NULL, + `status_ramai` ENUM('ramai','tidak_ramai') NOT NULL, + `buka_24_jam` ENUM('ya','tidak') NOT NULL, + `status_operasional` ENUM('buka','tutup') NOT NULL, + `nomor_hp` VARCHAR(20) NOT NULL, + `latitude` DOUBLE NOT NULL, + `longitude` DOUBLE NOT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_unicode_ci; diff --git a/04/tambah.php b/04/tambah.php new file mode 100644 index 0000000..0a2cb33 --- /dev/null +++ b/04/tambah.php @@ -0,0 +1,116 @@ + 'error', 'message' => 'Method tidak diizinkan.']); + exit; +} + +// ── Ambil & bersihkan input dari form ──────────────────────────────────────── +$nama_spbu = trim($_POST['nama_spbu'] ?? ''); +$status_ramai = trim($_POST['status_ramai'] ?? ''); +$buka_24_jam = trim($_POST['buka_24_jam'] ?? ''); +$status_operasional = trim($_POST['status_operasional'] ?? ''); +$nomor_hp = trim($_POST['nomor_hp'] ?? ''); +$latitude = trim($_POST['latitude'] ?? ''); +$longitude = trim($_POST['longitude'] ?? ''); + +// ── Validasi: semua field wajib terisi ─────────────────────────────────────── +if ( + empty($nama_spbu) || + empty($status_ramai) || + empty($buka_24_jam) || + empty($status_operasional) || + empty($nomor_hp) || + $latitude === '' || + $longitude === '' +) { + echo json_encode(['status' => 'error', 'message' => 'Semua field wajib diisi.']); + exit; +} + +// ── Validasi nilai ENUM (server-side) ──────────────────────────────────────── +$valid_ramai = ['ramai', 'tidak_ramai']; +$valid_24jam = ['ya', 'tidak']; +$valid_operasional = ['buka', 'tutup']; + +if (!in_array($status_ramai, $valid_ramai)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status ramai tidak valid.']); + exit; +} +if (!in_array($buka_24_jam, $valid_24jam)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai buka 24 jam tidak valid.']); + exit; +} +if (!in_array($status_operasional, $valid_operasional)) { + echo json_encode(['status' => 'error', 'message' => 'Nilai status operasional tidak valid.']); + exit; +} + +// ── Validasi latitude & longitude harus numerik ────────────────────────────── +if (!is_numeric($latitude) || !is_numeric($longitude)) { + echo json_encode(['status' => 'error', 'message' => 'Koordinat tidak valid.']); + exit; +} + +// Konversi ke float +$latitude = (float) $latitude; +$longitude = (float) $longitude; + +// ── Simpan ke database menggunakan prepared statement ──────────────────────── +// Prepared statement mencegah SQL Injection +$stmt = $koneksi->prepare( + "INSERT INTO spbu + (nama_spbu, status_ramai, buka_24_jam, status_operasional, nomor_hp, latitude, longitude) + VALUES (?, ?, ?, ?, ?, ?, ?)" +); + +// Bind parameter: s = string, d = double +$stmt->bind_param( + 'sssssdd', + $nama_spbu, + $status_ramai, + $buka_24_jam, + $status_operasional, + $nomor_hp, + $latitude, + $longitude +); + +// Eksekusi query +if ($stmt->execute()) { + $newId = $koneksi->insert_id; + + // Ambil data lengkap termasuk created_at & updated_at dari DB + $res = $koneksi->query("SELECT * FROM spbu WHERE id = $newId"); + $newData = $res->fetch_assoc(); + $newData['latitude'] = (float) $newData['latitude']; + $newData['longitude'] = (float) $newData['longitude']; + + // Kembalikan data lengkap agar JS bisa menentukan layer group yang tepat + echo json_encode([ + 'status' => 'success', + 'message' => 'Data SPBU berhasil ditambahkan.', + 'data' => $newData + ]); +} else { + echo json_encode(['status' => 'error', 'message' => 'Gagal menyimpan data: ' . $stmt->error]); +} + +$stmt->close(); +$koneksi->close(); +?> diff --git a/05/05_povertydb.sql b/05/05_povertydb.sql new file mode 100644 index 0000000..4f49305 --- /dev/null +++ b/05/05_povertydb.sql @@ -0,0 +1,373 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 12, 2026 at 10:30 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `05_povertydb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `penerima_bantuan` +-- + +CREATE TABLE `penerima_bantuan` ( + `id` int NOT NULL, + `user_id` int DEFAULT NULL, + `rumah_ibadah_id` int DEFAULT NULL, + `nama_penerima` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `nik` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `no_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `alamat` text COLLATE utf8mb4_unicode_ci NOT NULL, + `pekerjaan` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `penghasilan` int NOT NULL, + `jumlah_tanggungan` int NOT NULL, + `kondisi_rumah` enum('baik','sedang','rusak') COLLATE utf8mb4_unicode_ci NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `jarak_meter` double DEFAULT NULL, + `status_pengajuan` enum('menunggu','diterima','ditolak','dibatalkan') COLLATE utf8mb4_unicode_ci DEFAULT 'menunggu', + `status_hapus` enum('normal','menunggu','disetujui','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'normal', + `alasan_hapus` text COLLATE utf8mb4_unicode_ci, + `alasan_status` text COLLATE utf8mb4_unicode_ci, + `sumber_pengajuan` enum('mandiri','admin_rumah_ibadah') COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `penerima_bantuan` +-- + +INSERT INTO `penerima_bantuan` (`id`, `user_id`, `rumah_ibadah_id`, `nama_penerima`, `nik`, `no_hp`, `alamat`, `pekerjaan`, `penghasilan`, `jumlah_tanggungan`, `kondisi_rumah`, `jenis_bantuan`, `latitude`, `longitude`, `jarak_meter`, `status_pengajuan`, `status_hapus`, `alasan_hapus`, `alasan_status`, `sumber_pengajuan`, `created_at`, `updated_at`) VALUES +(1, 4, 1, 'Jotok', '60103011704940005', '08567891325', 'Jl. Bujang Malaka, RT/RW 024/008, Beringin, Kapuas, Sanggau', 'Tukang Antar Koran', 500000, 2, 'sedang', 'sembako', -0.07088082929060342, 109.36113953590394, 160.17, 'diterima', 'normal', NULL, '', 'mandiri', '2026-06-09 10:10:05', '2026-06-09 11:56:27'), +(2, 6, 5, 'caca', '124854', '410154', 'Gg. Kasturi Raja, Jl. Kenari', 'Mahasiswa', 0, 0, 'baik', 'uang_tunai', -0.020854501391015294, 109.33250427246095, 259.51, 'diterima', 'normal', NULL, '', 'mandiri', '2026-06-09 10:57:26', '2026-06-09 14:53:12'), +(4, NULL, 2, 'angel1', '123456', '123456', '1234', 'wwwwww', 0, 0, 'sedang', 'uang_tunai', -0.025105475576032914, 109.33623790740968, 128.27, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 11:27:12', '2026-06-09 11:28:31'), +(5, NULL, 2, 'angel2', '123', '123', 'ssss', 'sssss', 0, 0, 'sedang', 'uang_tunai', -0.025245714451433383, 109.33630228042604, 122.41, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 11:28:22', '2026-06-09 11:28:29'), +(6, 4, 1, 'k', 'g', 'u', 'k', 'i', 5, 7, 'baik', 'zakat', -0.06864918884517689, 109.3591332435608, 218.94, 'dibatalkan', 'disetujui', 'h', 'h', 'admin_rumah_ibadah', '2026-06-09 11:57:59', '2026-06-09 12:19:58'), +(7, 9, 5, 'penerima03', '124854', '123', 'Gang Merak II, Mariana, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', 'Mahasiswa', 50000, 1, 'sedang', 'sembako', -0.02033240043514327, 109.33441936969759, 136.81, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 14:52:40', '2026-06-09 14:53:12'), +(8, 10, 1, 'penerima04', '222223432', '13334341', 'Komplek Mitra Indah Utama I, Bangka Belitung Darat, Pontianak Tenggara, Pontianak, Kalimantan Barat, Kalimantan, 78124, Indonesia', 'wiraswasta', 1000000, 3, 'baik', 'sembako', -0.06962094072669782, 109.35720741748811, 299.36, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-10 01:22:26', '2026-06-10 01:22:34'), +(9, 11, 1, 'penerima05', '111111111111111', '11111111111111111', 'Nilam Permai, Sungai Raya, Kubu Raya, Kalimantan Barat, Kalimantan, 78124, Indonesia', 'disabilitas', 0, 1, 'rusak', 'uang_tunai', -0.07270815709759908, 109.3613098561764, 299.07, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-10 01:25:25', '2026-06-10 01:25:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `penyaluran_bantuan` +-- + +CREATE TABLE `penyaluran_bantuan` ( + `id` int NOT NULL, + `program_id` int DEFAULT NULL, + `penerima_id` int NOT NULL, + `rumah_ibadah_id` int NOT NULL, + `admin_id` int NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `nominal` int DEFAULT NULL, + `jumlah_item` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `bantuan_ke` int DEFAULT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `tanggal_penyaluran` date NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `penyaluran_bantuan` +-- + +INSERT INTO `penyaluran_bantuan` (`id`, `program_id`, `penerima_id`, `rumah_ibadah_id`, `admin_id`, `jenis_bantuan`, `nominal`, `jumlah_item`, `bantuan_ke`, `keterangan`, `tanggal_penyaluran`, `created_at`) VALUES +(1, 5, 5, 2, 5, 'zakat', 10000, NULL, 1, 'gg', '2026-06-09', '2026-06-09 11:33:36'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `program_bantuan` +-- + +CREATE TABLE `program_bantuan` ( + `id` int NOT NULL, + `rumah_ibadah_id` int DEFAULT NULL, + `nama_program` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `deskripsi` text COLLATE utf8mb4_unicode_ci, + `bulan` int NOT NULL, + `tahun` int NOT NULL, + `target_frekuensi` int DEFAULT '1', + `nominal` int DEFAULT NULL, + `jumlah_item` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `status_program` enum('aktif','selesai','dibatalkan') COLLATE utf8mb4_unicode_ci DEFAULT 'aktif', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `program_bantuan` +-- + +INSERT INTO `program_bantuan` (`id`, `rumah_ibadah_id`, `nama_program`, `jenis_bantuan`, `deskripsi`, `bulan`, `tahun`, `target_frekuensi`, `nominal`, `jumlah_item`, `status_program`, `created_at`, `updated_at`) VALUES +(3, 2, 'a', 'uang_tunai', 'a', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:29:28', '2026-06-09 11:29:28'), +(4, 2, 'b', 'uang_tunai', 'b', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:29:47', '2026-06-09 11:29:47'), +(5, 2, 'c', 'sembako', 'c', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:30:54', '2026-06-09 11:30:54'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `riwayat_status` +-- + +CREATE TABLE `riwayat_status` ( + `id` int NOT NULL, + `penerima_id` int NOT NULL, + `user_id` int NOT NULL, + `status_lama` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `status_baru` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `catatan` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `riwayat_status` +-- + +INSERT INTO `riwayat_status` (`id`, `penerima_id`, `user_id`, `status_lama`, `status_baru`, `catatan`, `created_at`) VALUES +(1, 1, 4, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 10:10:05'), +(2, 1, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 10:48:16'), +(3, 2, 6, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 10:57:26'), +(4, 2, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 10:59:04'), +(7, 4, 5, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:27:12'), +(8, 5, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:28:22'), +(9, 5, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:28:29'), +(10, 4, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:28:31'), +(11, 6, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:57:59'), +(12, 6, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:58:09'), +(13, 6, 1, 'diterima', 'dibatalkan', 'jotok 2 rmh', '2026-06-09 11:58:22'), +(14, 6, 1, 'dibatalkan', 'hapus_disetujui', 'h', '2026-06-09 12:19:58'), +(15, 7, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 14:52:40'), +(16, 7, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 14:52:49'), +(17, 8, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-10 01:22:26'), +(18, 8, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-10 01:22:34'), +(19, 9, 2, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-10 01:25:25'), +(20, 9, 2, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-10 01:25:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `rumah_ibadah` +-- + +CREATE TABLE `rumah_ibadah` ( + `id` int NOT NULL, + `user_id` int DEFAULT NULL, + `nama_rumah_ibadah` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL, + `agama` enum('islam','kristen','katolik','hindu','buddha','konghucu') COLLATE utf8mb4_unicode_ci NOT NULL, + `alamat` text COLLATE utf8mb4_unicode_ci NOT NULL, + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `status_pengajuan` enum('menunggu','diterima','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'menunggu', + `status_hapus` enum('normal','menunggu','disetujui','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'normal', + `alasan_hapus` text COLLATE utf8mb4_unicode_ci, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `rumah_ibadah` +-- + +INSERT INTO `rumah_ibadah` (`id`, `user_id`, `nama_rumah_ibadah`, `agama`, `alamat`, `nomor_hp`, `latitude`, `longitude`, `status_pengajuan`, `status_hapus`, `alasan_hapus`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 2, 'Gereja Katolik Paroki St. Sesilia', 'katolik', 'Jl. Sungai Raya Dalam', '0812345678913', -0.07052077848657445, 109.3597447872162, 'diterima', 'normal', NULL, '', '2026-06-09 10:11:48', '2026-06-09 10:48:18'), +(2, 5, 'GPIB', 'kristen', 'Jl. Kartini, Pontianak', '0812345678913', -0.02508443513561694, 109.33739125728609, 'diterima', 'normal', NULL, '', '2026-06-09 10:54:13', '2026-06-09 10:59:11'), +(4, 8, 'Gereja Elim', 'kristen', 'Jalan Rajawali, Tengah, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', '0812345678913', -0.02152069970142388, 109.33472514152527, 'ditolak', 'disetujui', 's', 's', '2026-06-09 14:50:50', '2026-06-09 14:51:16'), +(5, 8, 'Gereja Elim', 'kristen', 'Jalan Rajawali, Tengah, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', '0812345678913', -0.02151988492191236, 109.33474123477937, 'diterima', 'normal', NULL, 'Ditambahkan langsung oleh super admin.', '2026-06-09 14:53:12', '2026-06-09 14:53:12'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `users` +-- + +CREATE TABLE `users` ( + `id` int NOT NULL, + `nama` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `role` enum('super_admin','admin_rumah_ibadah','penerima') COLLATE utf8mb4_unicode_ci NOT NULL, + `status_akun` enum('aktif','nonaktif') COLLATE utf8mb4_unicode_ci DEFAULT 'aktif', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `users` +-- + +INSERT INTO `users` (`id`, `nama`, `username`, `password`, `role`, `status_akun`, `created_at`, `updated_at`) VALUES +(1, 'Super Admin Dinas Sosial', 'superadmin', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'super_admin', 'aktif', '2026-06-09 10:04:24', '2026-06-09 14:48:23'), +(2, 'admingerejasesilia', 'admingerejasesilia', '$2y$10$Pt4VTj7yDLt1xabM5WwGJuBo.gat807fkV0Q.KiGKv.V0tRbo1SHW', 'admin_rumah_ibadah', 'aktif', '2026-06-09 10:06:38', '2026-06-10 01:23:46'), +(4, 'penerima01', 'penerima01', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'penerima', 'nonaktif', '2026-06-09 10:08:28', '2026-06-10 01:18:35'), +(5, 'admingpib', 'admingpib', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'admin_rumah_ibadah', 'nonaktif', '2026-06-09 10:52:35', '2026-06-09 14:47:58'), +(6, 'penerima02', 'penerima02', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'penerima', 'aktif', '2026-06-09 10:55:15', '2026-06-09 14:47:48'), +(8, 'Admin Gereja Elim', 'admingerejaelim', '$2y$10$hiPcHEFAb2lPq3WKLra1MuZ8220s/UY.3PG24ZIrmbxrbp2imboGO', 'admin_rumah_ibadah', 'aktif', '2026-06-09 14:49:55', '2026-06-09 14:49:55'), +(9, 'penerima03', 'penerima03', '$2y$10$.50kERk9OSPvJhe.9IFxd.3qi2j5spTxtM36Kx1mI7Q3HkeVlyzvm', 'penerima', 'nonaktif', '2026-06-09 14:51:50', '2026-06-10 01:18:20'), +(10, 'penerima04', 'penerima04', '$2y$10$Q0yfgX7/KnCa4sxnz/f5YupK2aVyKWLDPO8CD/mdQlCSOTRbzLBli', 'penerima', 'aktif', '2026-06-10 01:20:04', '2026-06-10 01:20:04'), +(11, 'penerima05', 'penerima05', '$2y$10$0UJ97IYsDsS/K/qJbHJsiO938TFSU2AorU0fqxAawnbpwhJN/qBqi', 'penerima', 'aktif', '2026-06-10 01:21:01', '2026-06-10 01:21:01'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_pb_user` (`user_id`), + ADD KEY `idx_pb_ri` (`rumah_ibadah_id`), + ADD KEY `idx_pb_status` (`status_pengajuan`), + ADD KEY `idx_pb_jenis` (`jenis_bantuan`); + +-- +-- Indexes for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_pen_program` (`program_id`), + ADD KEY `idx_pen_penerima` (`penerima_id`), + ADD KEY `idx_pen_ri` (`rumah_ibadah_id`), + ADD KEY `idx_pen_admin` (`admin_id`), + ADD KEY `idx_pen_tanggal` (`tanggal_penyaluran`); + +-- +-- Indexes for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_prog_ri` (`rumah_ibadah_id`), + ADD KEY `idx_prog_periode` (`bulan`,`tahun`), + ADD KEY `idx_prog_status` (`status_program`); + +-- +-- Indexes for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_rw_penerima` (`penerima_id`), + ADD KEY `idx_rw_user` (`user_id`); + +-- +-- Indexes for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_ri_user` (`user_id`), + ADD KEY `idx_ri_status` (`status_pengajuan`), + ADD KEY `idx_ri_agama` (`agama`); + +-- +-- Indexes for table `users` +-- +ALTER TABLE `users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `username` (`username`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + +-- +-- AUTO_INCREMENT for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- AUTO_INCREMENT for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; + +-- +-- AUTO_INCREMENT for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- AUTO_INCREMENT for table `users` +-- +ALTER TABLE `users` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + ADD CONSTRAINT `fk_pb_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pb_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- +-- Constraints for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + ADD CONSTRAINT `fk_pen_admin` FOREIGN KEY (`admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_penerima` FOREIGN KEY (`penerima_id`) REFERENCES `penerima_bantuan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_program` FOREIGN KEY (`program_id`) REFERENCES `program_bantuan` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + ADD CONSTRAINT `fk_prog_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- +-- Constraints for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + ADD CONSTRAINT `fk_rw_penerima` FOREIGN KEY (`penerima_id`) REFERENCES `penerima_bantuan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_rw_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + ADD CONSTRAINT `fk_ri_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/05/PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING_PROJECT_05.md b/05/PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING_PROJECT_05.md new file mode 100644 index 0000000..520dda1 --- /dev/null +++ b/05/PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING_PROJECT_05.md @@ -0,0 +1,723 @@ +# PANDUAN INSTALASI LOCALHOST SAMPAI HOSTING + +**Project:** 05 - WebGIS Poverty Mapping +**Teknologi:** PHP Native, MySQL/MariaDB, Leaflet.js, HTML, CSS, JavaScript Vanilla +**Folder lokal:** `webgis-poverty/05/` +**URL lokal:** `http://localhost/webgis-poverty/05/` + +--- + +## 1. Ringkasan Aplikasi + +Aplikasi ini adalah WebGIS Poverty Mapping untuk memetakan penerima bantuan dan rumah ibadah sebagai pemberi/penyalur bantuan. Sistem memiliki tiga role utama: + +1. **Super Admin** + - Mengelola seluruh data. + - Menyetujui/menolak rumah ibadah. + - Menyetujui/menolak penerima bantuan. + - Mengelola akun admin rumah ibadah dan penerima. + - Mengelola program bantuan dan laporan. + - Menyetujui/menolak pengajuan hapus marker/pin. + +2. **Admin Rumah Ibadah** + - Mengelola rumah ibadah miliknya. + - Mengelola akun penerima. + - Mengajukan/mengelola penerima dalam radius rumah ibadahnya. + - Membuat program bantuan. + - Mencatat penyaluran bantuan. + - Melihat laporan dan riwayat sesuai rumah ibadah yang dikelola. + +3. **Penerima** + - Registrasi akun. + - Mengajukan titik lokasi rumah melalui peta. + - Melihat status pengajuan. + - Melihat riwayat bantuan. + - Mengajukan hapus pin lokasi miliknya. + +Aturan utama sistem: + +- Radius tanggung jawab rumah ibadah adalah **300 meter**. +- Penerima otomatis dikaitkan ke **rumah ibadah terdekat** dalam radius 300 meter. +- Satu akun penerima hanya boleh memiliki **satu pin aktif**. +- Admin rumah ibadah dapat mengelola lebih dari satu rumah ibadah. +- Data yang sudah punya relasi tidak langsung dihapus permanen, tetapi dinonaktifkan atau diberi status hapus agar riwayat audit tetap aman. + +--- + +## 2. Struktur File Project + +Struktur project terbaru: + +```text +05/ +├── index.php +├── login.php +├── logout.php +├── register.php +├── dashboard.php +├── koneksi.php +├── auth.php +│ +├── ambil_data_peta.php +├── ambil_laporan_penyaluran.php +├── ambil_program_bantuan.php +├── ambil_riwayat.php +├── ambil_user.php +│ +├── ajukan_rumah_ibadah.php +├── hapus_rumah_ibadah.php +├── update_status_rumah_ibadah.php +│ +├── tambah_penerima.php +├── edit_penerima.php +├── hapus_penerima.php +├── update_status_penerima.php +│ +├── tambah_program_bantuan.php +├── edit_program_bantuan.php +├── hapus_program_bantuan.php +│ +├── tambah_penyaluran.php +│ +├── tambah_user.php +├── edit_user.php +├── hapus_user.php +│ +├── backup_sql/ +│ ├── schema.sql +│ ├── migration_fix_multi_ri_pin.sql +│ ├── migration_revisi_persetujuan_hapus.sql +│ └── migration_revisi_sinkronisasi.sql +│ +├── RANGKUMAN_PENTING_PROJECT_05.md +└── PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING.md +``` + +--- + +## 3. Persiapan Localhost dengan Laragon + +1. Buka Laragon. +2. Klik **Start All** agar Apache dan MySQL/MariaDB aktif. +3. Buka folder Laragon: + +```text +C:\laragon\www\ +``` + +4. Buat folder project: + +```text +C:\laragon\www\webgis-poverty\05\ +``` + +5. Ekstrak isi ZIP project ke folder `05`. +6. Pastikan file `index.php`, `login.php`, `dashboard.php`, dan `koneksi.php` berada langsung di dalam folder `05`, bukan di dalam folder tambahan. + +Struktur yang benar: + +```text +C:\laragon\www\webgis-poverty\05\index.php +C:\laragon\www\webgis-poverty\05\login.php +C:\laragon\www\webgis-poverty\05\dashboard.php +C:\laragon\www\webgis-poverty\05\koneksi.php +``` + +Struktur yang salah: + +```text +C:\laragon\www\webgis-poverty\05\05\index.php +``` + +Jika terjadi struktur salah, pindahkan semua file dari folder `05\05\` ke folder `05\` utama. + +--- + +## 4. Import Database di Localhost + +### 4.1 Buka phpMyAdmin + +1. Jalankan Laragon. +2. Klik **Database** atau buka browser: + +```text +http://localhost/phpmyadmin +``` + +3. Login phpMyAdmin Laragon: + +```text +Username : root +Password : kosongkan +``` + +### 4.2 Pilihan File SQL + +Ada dua pilihan import database: + +#### Opsi A - Database Demo Lengkap + +Gunakan file: + +```text +05_povertydb.sql +``` + +Opsi ini cocok jika ingin langsung ada data contoh, akun demo, rumah ibadah, penerima, program, dan laporan. + +#### Opsi B - Struktur Kosong dari ZIP + +Gunakan file: + +```text +backup_sql/schema.sql +``` + +Opsi ini cocok jika ingin membuat database dari awal. Setelah import, data contoh mungkin belum lengkap. + +Jika ada file `05_povertydb.sql`, disarankan memakai file tersebut untuk demo karena akun-akun contoh sudah tersedia. + +### 4.3 Cara Import + +1. Buka phpMyAdmin. +2. Klik tab **Import**. +3. Pilih file SQL. +4. Klik **Go**. +5. Pastikan database `05_povertydb` berhasil dibuat. +6. Pastikan tabel berikut muncul: + +```text +users +rumah_ibadah +penerima_bantuan +program_bantuan +penyaluran_bantuan +riwayat_status +``` + +--- + +## 5. Konfigurasi Database Localhost + +Buka file: + +```text +koneksi.php +``` + +Untuk Laragon, gunakan konfigurasi berikut: + +```php +$host = "localhost"; +$user = "root"; +$pass = ""; +$db = "05_povertydb"; +``` + +Simpan file tersebut. + +--- + +## 6. Akun Login Default + +Semua password akun demo adalah: + +```text +password +``` + +Akun utama: + +```text +Role : Super Admin +Username : superadmin +Password : password +``` + +Contoh akun admin rumah ibadah jika memakai database demo: + +```text +Username : admingerejasesilia +Password : password +Role : admin_rumah_ibadah +``` + +```text +Username : admingerejaelim +Password : password +Role : admin_rumah_ibadah +``` + +Contoh akun penerima jika memakai database demo: + +```text +Username : penerima02 +Password : password +Role : penerima +``` + +```text +Username : penerima04 +Password : password +Role : penerima +``` + +```text +Username : penerima05 +Password : password +Role : penerima +``` + +Catatan: + +- Akun dengan status `nonaktif` tidak bisa login. +- Password disimpan menggunakan `password_hash()` dan diverifikasi menggunakan `password_verify()`. +- Jangan menulis password plain text langsung di database. + +--- + +## 7. Menjalankan Aplikasi di Localhost + +1. Pastikan Laragon sudah **Start All**. +2. Buka browser. +3. Akses: + +```text +http://localhost/webgis-poverty/05/ +``` + +4. Jika diarahkan ke login, masuk menggunakan: + +```text +Username : superadmin +Password : password +``` + +5. Setelah login, menu utama: + +```text +index.php = halaman peta WebGIS +dashboard.php = dashboard, akun, program, penyaluran, laporan, dan riwayat +logout.php = keluar dari sistem +``` + +--- + +## 8. Pengujian Fitur Localhost + +### 8.1 Login Super Admin + +1. Buka halaman login. +2. Masukkan username `superadmin`. +3. Masukkan password `password`. +4. Pastikan masuk ke dashboard. + +### 8.2 Kelola Akun + +1. Login sebagai super admin. +2. Buka **Dashboard**. +3. Cari bagian **Kelola Akun Pengguna**. +4. Tambah akun admin rumah ibadah atau penerima. +5. Gunakan password minimal 6 karakter. Untuk demo, isi `password`. +6. Simpan. +7. Coba login menggunakan akun baru. + +### 8.3 Tambah Rumah Ibadah + +1. Login sebagai super admin. +2. Buka **Peta**. +3. Klik lokasi pada peta. +4. Pilih **Tambah Rumah Ibadah**. +5. Isi data rumah ibadah. +6. Masukkan username admin rumah ibadah. +7. Simpan. +8. Jika super admin yang menambah, status rumah ibadah langsung dapat diterima. + +### 8.4 Pengajuan Rumah Ibadah oleh Admin + +1. Login sebagai admin rumah ibadah. +2. Buka peta. +3. Klik lokasi rumah ibadah. +4. Pilih **Ajukan Rumah Ibadah**. +5. Isi data. +6. Pengajuan akan berstatus `menunggu`. +7. Super admin harus menyetujui agar rumah ibadah aktif. + +### 8.5 Pengajuan Penerima + +1. Login sebagai penerima. +2. Buka peta. +3. Klik lokasi rumah. +4. Pilih **Simpan Pengajuan**. +5. Isi data penerima. +6. Sistem mencari rumah ibadah terdekat dalam radius 300 meter. +7. Status awal penerima adalah `menunggu`. +8. Super admin atau admin rumah ibadah terkait dapat menyetujui/menolak. + +### 8.6 Program Bantuan + +1. Login sebagai super admin atau admin rumah ibadah. +2. Buka dashboard. +3. Isi form **Buat Program Bantuan**. +4. Pilih rumah ibadah, jenis bantuan, bulan, tahun, target frekuensi, nominal/item, dan deskripsi. +5. Simpan. + +### 8.7 Penyaluran Bantuan + +1. Pastikan penerima sudah berstatus `diterima`. +2. Pastikan penerima berada dalam rumah ibadah yang sama dengan program. +3. Buka form **Catat Penyaluran Bantuan**. +4. Pilih program. +5. Pilih penerima. +6. Isi tanggal dan keterangan. +7. Simpan. +8. Sistem otomatis menghitung bantuan ke-berapa pada program tersebut. + +### 8.8 Laporan Bulanan + +1. Buka dashboard. +2. Cari bagian **Monitoring Penyaluran Per Periode**. +3. Pilih bulan dan tahun. +4. Gunakan filter rumah ibadah, agama, program, jenis bantuan, dan status penyaluran. +5. Klik tampilkan/muat laporan. +6. Laporan menampilkan penerima yang belum diberi, sudah diberi, belum memenuhi target, sesuai target, atau melebihi target. + +### 8.9 Pengajuan Hapus Marker/Pin + +1. Super admin dapat menghapus marker langsung. +2. Admin rumah ibadah hanya dapat mengajukan hapus rumah ibadah miliknya atau pin penerima yang dikelola. +3. Penerima dapat mengajukan hapus pin miliknya sendiri. +4. Pengajuan hapus akan masuk ke panel persetujuan super admin. +5. Jika disetujui, marker/pin hilang dari peta aktif. + +--- + +## 9. Persiapan Sebelum Hosting + +Sebelum upload ke hosting, pastikan: + +```text +[ ] Aplikasi berjalan di localhost. +[ ] Database berhasil diimport. +[ ] Login superadmin berhasil. +[ ] Peta muncul. +[ ] Dashboard muncul. +[ ] Tambah akun berhasil. +[ ] Tambah rumah ibadah berhasil. +[ ] Tambah penerima berhasil. +[ ] Program bantuan berhasil dibuat. +[ ] Penyaluran bantuan berhasil dicatat. +[ ] Laporan berhasil tampil. +``` + +File yang wajib diupload ke hosting: + +```text +index.php +login.php +logout.php +register.php +dashboard.php +koneksi.php +auth.php +ambil_data_peta.php +ambil_laporan_penyaluran.php +ambil_program_bantuan.php +ambil_riwayat.php +ambil_user.php +ajukan_rumah_ibadah.php +hapus_rumah_ibadah.php +update_status_rumah_ibadah.php +tambah_penerima.php +edit_penerima.php +hapus_penerima.php +update_status_penerima.php +tambah_program_bantuan.php +edit_program_bantuan.php +hapus_program_bantuan.php +tambah_penyaluran.php +tambah_user.php +edit_user.php +hapus_user.php +``` + +File yang tidak wajib diupload ke hosting setelah database berhasil diimport: + +```text +backup_sql/ +05_povertydb.sql +RANGKUMAN_PENTING_PROJECT_05.md +PANDUAN_INSTALASI_LOCALHOST_SAMPAI_HOSTING.md +file ZIP backup +``` + +Untuk keamanan, file SQL dan ZIP backup sebaiknya tidak dibiarkan terbuka di `public_html`. + +--- + +## 10. Upload ke Hosting cPanel + +1. Login ke cPanel hosting. +2. Buka **File Manager**. +3. Masuk ke folder: + +```text +public_html +``` + +4. Buat folder: + +```text +webgis-poverty +``` + +5. Di dalamnya buat folder: + +```text +05 +``` + +6. Upload file aplikasi ke: + +```text +public_html/webgis-poverty/05/ +``` + +7. Jika upload berupa ZIP, klik kanan ZIP lalu pilih **Extract**. +8. Pastikan `index.php` berada langsung di folder `05`. + +URL setelah upload: + +```text +https://namadomain.com/webgis-poverty/05/ +``` + +--- + +## 11. Membuat Database Hosting + +1. Buka menu **MySQL Databases** di cPanel. +2. Buat database baru, contoh: + +```text +cpaneluser_05_povertydb +``` + +3. Buat user database baru, contoh: + +```text +cpaneluser_userdb +``` + +4. Buat password database. +5. Tambahkan user ke database. +6. Berikan akses **ALL PRIVILEGES**. +7. Catat data berikut: + +```text +Host database : localhost +Nama database : cpaneluser_05_povertydb +Username database : cpaneluser_userdb +Password database : password database hosting +``` + +--- + +## 12. Import Database di Hosting + +1. Buka **phpMyAdmin** dari cPanel. +2. Pilih database hosting yang sudah dibuat. +3. Klik tab **Import**. +4. Upload file SQL. +5. Klik **Go**. +6. Pastikan semua tabel muncul. + +Jika hosting menolak perintah: + +```sql +CREATE DATABASE IF NOT EXISTS `05_povertydb`; +USE `05_povertydb`; +``` + +maka hapus atau komentari dua bagian tersebut dari file SQL, lalu import ulang pada database yang sudah dipilih di phpMyAdmin. + +--- + +## 13. Mengubah koneksi.php untuk Hosting + +Buka `koneksi.php`, lalu ubah konfigurasi menjadi data hosting. + +Contoh: + +```php +$host = "localhost"; +$user = "cpaneluser_userdb"; +$pass = "PasswordDatabaseHosting"; +$db = "cpaneluser_05_povertydb"; +``` + +Simpan file. + +--- + +## 14. Akses Aplikasi Online + +Jika foldernya: + +```text +public_html/webgis-poverty/05/ +``` + +maka URL biasanya: + +```text +https://namadomain.com/webgis-poverty/05/ +``` + +Jika langsung diletakkan di `public_html`, URL menjadi: + +```text +https://namadomain.com/ +``` + +Jika memakai subdomain, misalnya `gis.namadomain.com`, arahkan document root subdomain ke folder aplikasi. + +--- + +## 15. Checklist Setelah Hosting + +```text +[ ] Halaman login terbuka. +[ ] Login superadmin berhasil. +[ ] Dashboard terbuka. +[ ] Peta Leaflet muncul. +[ ] Marker rumah ibadah muncul. +[ ] Marker penerima muncul. +[ ] Tambah akun berhasil. +[ ] Tambah/ajukan rumah ibadah berhasil. +[ ] Setujui/tolak rumah ibadah berhasil. +[ ] Tambah/ajukan penerima berhasil. +[ ] Setujui/tolak penerima berhasil. +[ ] Buat program bantuan berhasil. +[ ] Catat penyaluran bantuan berhasil. +[ ] Laporan bulanan tampil. +[ ] Riwayat tampil. +[ ] Logout berhasil. +[ ] Aplikasi bisa dibuka dari HP. +``` + +--- + +## 16. Troubleshooting + +### 16.1 Halaman Blank Putih + +Penyebab umum: + +- Error PHP. +- Koneksi database gagal. +- File belum lengkap. +- Ada output sebelum `session_start()`. + +Solusi: + +- Cek file `error_log` di hosting. +- Pastikan `koneksi.php` benar. +- Pastikan semua file PHP terupload. + +### 16.2 Database Connection Failed + +Solusi: + +- Cek nama database. +- Cek username database. +- Cek password database. +- Cek host database, biasanya `localhost`. +- Pastikan user database sudah diberi **ALL PRIVILEGES**. + +### 16.3 Login Gagal + +Solusi: + +- Pastikan username benar. +- Pastikan akun berstatus `aktif`. +- Pastikan password adalah `password` untuk akun demo. +- Pastikan password di database dibuat memakai `password_hash()`. + +### 16.4 Peta Tidak Muncul + +Solusi: + +- Pastikan internet aktif karena Leaflet memakai CDN. +- Pastikan elemen peta memiliki tinggi. +- Buka Console browser untuk melihat error JavaScript. + +### 16.5 Marker Tidak Muncul + +Solusi: + +- Cek latitude dan longitude. +- Cek status pengajuan. +- Data `ditolak` atau `status_hapus='disetujui'` memang tidak tampil sebagai marker aktif. +- Cek response `ambil_data_peta.php` melalui tab Network browser. + +### 16.6 Import SQL Gagal + +Solusi: + +- Pastikan database sudah dipilih sebelum import. +- Jika error `CREATE DATABASE`, hapus baris `CREATE DATABASE` dan `USE`. +- Jika error `IF NOT EXISTS` pada `ALTER TABLE`, gunakan file migration versi kompatibel. +- Jika ada pin aktif ganda, rapikan data penerima terlebih dahulu sebelum membuat unique key satu pin aktif. + +### 16.7 Fetch Error + +Solusi: + +- Pastikan file PHP tujuan ada. +- Pastikan path fetch memakai path relatif, misalnya: + +```javascript +fetch('ambil_data_peta.php') +``` + +Jangan memakai path localhost saat sudah hosting: + +```javascript +fetch('http://localhost/webgis-poverty/05/ambil_data_peta.php') +``` + +--- + +## 17. Keamanan Dasar Setelah Online + +Setelah aplikasi online: + +1. Ganti password akun penting jika sudah masuk tahap produksi. +2. Jangan biarkan file `.sql` dan `.zip` di folder publik. +3. Jangan tampilkan detail error kepada pengguna umum. +4. Gunakan HTTPS. +5. Pastikan semua akun admin menggunakan password yang kuat. +6. Nonaktifkan akun yang tidak digunakan. +7. Backup database secara berkala. + +--- + +## 18. Ringkasan Akhir + +Urutan instalasi yang benar: + +```text +1. Ekstrak project ke C:\laragon\www\webgis-poverty\05\ +2. Start All di Laragon. +3. Import database melalui phpMyAdmin. +4. Cek koneksi.php. +5. Buka http://localhost/webgis-poverty/05/ +6. Login superadmin / password. +7. Uji peta, dashboard, akun, rumah ibadah, penerima, program, penyaluran, dan laporan. +8. Upload file PHP ke hosting. +9. Buat database hosting. +10. Import SQL di hosting. +11. Ubah koneksi.php sesuai hosting. +12. Hapus file SQL/ZIP dari public_html setelah selesai. +``` diff --git a/05/RANGKUMAN_PENTING_PROJECT_05.md b/05/RANGKUMAN_PENTING_PROJECT_05.md new file mode 100644 index 0000000..fdd5059 --- /dev/null +++ b/05/RANGKUMAN_PENTING_PROJECT_05.md @@ -0,0 +1,230 @@ +# RANGKUMAN PENTING PROJECT 05 - WebGIS Poverty Mapping + +File ini merangkum catatan penting saja dari beberapa file catatan revisi/migrasi. + +--- + +## 1. File SQL yang Penting Disimpan + +Simpan file SQL berikut sebagai backup database dan migrasi: + +1. `schema.sql` + - Struktur database dasar. + - Dipakai jika ingin instal ulang database dari awal. + +2. `migration_fix_multi_ri_pin.sql` + - Memperbaiki masalah admin rumah ibadah yang punya lebih dari satu rumah ibadah. + - Menambahkan aturan satu akun penerima hanya boleh punya satu pin aktif. + +3. `migration_revisi_persetujuan_hapus.sql` + - Menambahkan fitur pengajuan/persetujuan hapus marker rumah ibadah dan pin penerima. + +4. `migration_revisi_sinkronisasi.sql` + - Memperbaiki sinkronisasi penerima dengan rumah ibadah terdekat. + +Rekomendasi: +- Jangan letakkan file SQL di folder publik saat hosting. +- Simpan di folder backup lokal, misalnya `backup_sql/`. + +--- + +## 2. Perbaikan Paling Penting pada Sistem + +### A. Admin Rumah Ibadah Multi-RI + +Masalah lama: +- Admin rumah ibadah hanya terbaca punya 1 rumah ibadah karena query memakai `LIMIT 1`. +- Akibatnya, pin/marker dari rumah ibadah lain tidak muncul. + +Perbaikan: +- Sistem sekarang mendukung admin yang mengelola lebih dari satu rumah ibadah. +- Query memakai daftar ID rumah ibadah, bukan hanya 1 ID. + +Dampak: +- Pin penerima dan rumah ibadah milik admin tampil lebih lengkap. +- Laporan, riwayat, program bantuan, dan penyaluran mengikuti semua rumah ibadah milik admin. + +--- + +### B. Satu Akun Penerima Hanya Boleh Satu Pin Aktif + +Masalah lama: +- Satu akun penerima bisa punya lebih dari satu pin aktif. +- Ini menyebabkan data tidak sinkron dan penerima bisa muncul di beberapa rumah ibadah. + +Perbaikan: +- Sebelum menambah pin, sistem mengecek apakah user penerima sudah punya pin aktif. +- Pin aktif yang dimaksud adalah status `menunggu` atau `diterima`, dan belum disetujui hapus. +- Database juga ditambah unique key agar satu user tidak bisa punya lebih dari satu pin aktif. + +--- + +### C. Sinkronisasi Radius 300 Meter + +Masalah lama: +- Penerima bisa terhubung ke rumah ibadah yang salah. +- Jika radius beberapa rumah ibadah saling beririsan, sistem belum selalu memilih yang paling dekat. + +Perbaikan: +- Penerima dihitung ulang berdasarkan rumah ibadah aktif terdekat dalam radius 300 meter. +- Jika rumah ibadah dihapus, penerima dilepas lalu dicari ulang ke rumah ibadah aktif terdekat lain. + +--- + +### D. Persetujuan Hapus Marker/Pin + +Alur baru: +- Super admin bisa menghapus marker rumah ibadah dan pin penerima secara langsung. +- Admin rumah ibadah hanya bisa mengajukan hapus rumah ibadah/penerima. +- Penerima bisa mengajukan hapus pin miliknya sendiri. +- Pengajuan hapus harus disetujui super admin. + +Dampak: +- Data tidak langsung hilang sembarangan. +- Ada kontrol dari super admin. +- Marker yang sudah disetujui hapus tidak tampil lagi di peta dan laporan aktif. + +--- + +### E. CRUD Akun + +Super admin dapat mengelola: +- Akun admin rumah ibadah. +- Akun penerima. + +Admin rumah ibadah dapat mengelola: +- Akun penerima saja. + +Fitur: +- Tambah akun. +- Lihat daftar akun. +- Filter role/status. +- Cari nama/username. +- Edit akun. +- Hapus akun jika belum punya relasi data. +- Nonaktifkan akun jika sudah punya relasi agar audit data tidak rusak. + +--- + +### F. Validasi Program dan Penyaluran Bantuan + +Perbaikan penting: +- Program bantuan tidak otomatis mengambil rumah ibadah pertama. +- Admin yang punya banyak rumah ibadah harus memilih rumah ibadah. +- Penyaluran hanya bisa dicatat jika program dan penerima berada di rumah ibadah yang sama. +- Tombol "Catat" tidak muncul lagi jika program sudah selesai. +- Dropdown penerima difilter sesuai rumah ibadah dari program yang dipilih. + +--- + +### G. Perbaikan Logika NULL + +Masalah lama: +- Nilai kosong seperti nominal, jarak meter, atau rumah_ibadah_id bisa tersimpan sebagai `0`, bukan `NULL`. + +Perbaikan: +- Query INSERT/UPDATE dipisah agar nilai kosong benar-benar masuk sebagai `NULL`. + +Dampak: +- Data lebih bersih. +- Tidak muncul rumah_ibadah_id = 0 atau jarak_meter = 0 yang menyesatkan. + +--- + +### H. Validasi Client-side + +Ditambahkan validasi sebelum data dikirim: +- Program wajib dipilih. +- Penerima wajib dipilih. +- Nama program wajib diisi. +- Bulan harus 1-12. +- Tahun minimal 2000. +- Nama, username, dan password wajib saat membuat akun baru. +- Password minimal 6 karakter. + +--- + +## 3. Catatan Error SQL + +### Error #1044 information_schema + +Penyebab: +- Akun MySQL tidak punya akses membaca `INFORMATION_SCHEMA`. + +Solusi: +- Gunakan file migrasi versi manual. +- Atau jalankan `jalankan_migrasi_fix.php` jika file itu tersedia. +- Setelah selesai, hapus `jalankan_migrasi_fix.php`. + +--- + +### Error #1064 IF NOT EXISTS + +Penyebab: +- Versi MySQL/MariaDB tidak mendukung syntax: + `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` + +Solusi: +- Gunakan file `migration_fix_multi_ri_pin.sql` versi kompatibel. +- Jika ada penerima dengan pin aktif ganda, rapikan dulu lewat dashboard super admin. +- Setelah itu jalankan ulang migration. + +--- + +## 4. File Catatan yang Bisa Digabung/Hapus + +Setelah rangkuman ini dibuat, file catatan berikut boleh dipindahkan ke backup: + +- `CATATAN_FIXED_MULTI_RI.md` +- `CATATAN_PERBAIKAN_LOGIKA.md` +- `CATATAN_REVISI_CRUD_AKUN.md` +- `CATATAN_REVISI_PERSETUJUAN_HAPUS.md` +- `CATATAN_SQL_ERROR_1044.md` +- `CATATAN_SQL_ERROR_1064.md` + +Jangan langsung buang permanen jika proyek masih diperiksa dosen/penguji. + +--- + +## 5. Rekomendasi Struktur Folder + +Gunakan struktur seperti ini: + +```text +05/ +├── index.php +├── login.php +├── register.php +├── koneksi.php +├── auth.php +├── tambah_penerima.php +├── tambah_penyaluran.php +├── tambah_program_bantuan.php +├── edit_user.php +├── hapus_user.php +├── ... +└── backup_dokumentasi/ + ├── RANGKUMAN_PENTING_PROJECT_05.md + └── backup_sql/ + ├── schema.sql + ├── migration_fix_multi_ri_pin.sql + ├── migration_revisi_persetujuan_hapus.sql + └── migration_revisi_sinkronisasi.sql +``` + +--- + +## 6. Kesimpulan + +Yang paling penting disimpan: +1. `schema.sql` +2. Semua file `migration_*.sql` +3. Satu file rangkuman ini +4. File PHP aplikasi utama + +Yang tidak wajib ada di folder utama aplikasi: +- File catatan `.md` +- File SQL backup +- File migrasi yang sudah selesai dijalankan + +Untuk hosting, file SQL dan catatan revisi sebaiknya tidak ikut berada di folder publik. diff --git a/05/SKPL_SRS_WEBGIS_POVERTY_MAPPING_PROJECT_05.md b/05/SKPL_SRS_WEBGIS_POVERTY_MAPPING_PROJECT_05.md new file mode 100644 index 0000000..e1fe2ae --- /dev/null +++ b/05/SKPL_SRS_WEBGIS_POVERTY_MAPPING_PROJECT_05.md @@ -0,0 +1,632 @@ +# SKPL / SRS WEBGIS POVERTY MAPPING PROJECT 05 + +**Nama Dokumen:** Spesifikasi Kebutuhan Perangkat Lunak / Software Requirements Specification +**Nama Sistem:** WebGIS Poverty Mapping +**Versi:** 1.0 +**Teknologi:** PHP Native, MySQL/MariaDB, Leaflet.js, HTML, CSS, JavaScript Vanilla +**Lingkungan Pengembangan:** Laragon Localhost dan Hosting cPanel + +--- + +## 1. Pendahuluan + +### 1.1 Tujuan Dokumen + +Dokumen ini menjelaskan kebutuhan perangkat lunak untuk aplikasi **WebGIS Poverty Mapping Project 05**. Dokumen ini dapat digunakan sebagai acuan pengembangan, pengujian, instalasi, dan evaluasi aplikasi. + +### 1.2 Ruang Lingkup Sistem + +WebGIS Poverty Mapping adalah aplikasi berbasis web untuk memetakan rumah ibadah dan penerima bantuan. Sistem digunakan untuk membantu proses pendataan, validasi, pengelolaan program bantuan, penyaluran bantuan, dan pelaporan penerima yang sudah atau belum memperoleh bantuan. + +Sistem memiliki fitur utama: + +1. Login dan manajemen role pengguna. +2. Registrasi akun penerima. +3. Pengelolaan akun pengguna. +4. Pemetaan rumah ibadah dan penerima bantuan menggunakan Leaflet. +5. Pengajuan dan persetujuan rumah ibadah. +6. Pengajuan dan persetujuan penerima bantuan. +7. Sinkronisasi penerima ke rumah ibadah terdekat dalam radius 300 meter. +8. Pengelolaan program bantuan. +9. Pencatatan penyaluran bantuan. +10. Laporan penyaluran bantuan per periode. +11. Riwayat status dan penyaluran. +12. Pengajuan dan persetujuan hapus marker/pin. + +### 1.3 Definisi Istilah + +| Istilah | Definisi | +|---|---| +| WebGIS | Sistem informasi geografis berbasis web. | +| Rumah Ibadah | Entitas pemberi/penyalur bantuan yang memiliki lokasi pada peta. | +| Penerima | Pengguna atau warga yang mengajukan bantuan dan memiliki titik lokasi. | +| Super Admin | Pengguna dengan akses tertinggi. | +| Admin Rumah Ibadah | Pengguna yang mengelola satu atau lebih rumah ibadah. | +| Radius 300 Meter | Jarak maksimal penerima agar dapat dikaitkan dengan rumah ibadah. | +| Marker | Titik rumah ibadah pada peta. | +| Pin | Titik penerima pada peta. | +| Program Bantuan | Program penyaluran bantuan pada periode tertentu. | +| Penyaluran Bantuan | Catatan pemberian bantuan kepada penerima. | +| SKPL | Spesifikasi Kebutuhan Perangkat Lunak. | +| SRS | Software Requirements Specification. | + +### 1.4 Referensi Teknologi + +Sistem dibangun menggunakan: + +1. PHP Native tanpa framework. +2. MySQL/MariaDB sebagai basis data. +3. Leaflet.js sebagai library peta. +4. HTML, CSS, dan JavaScript Vanilla. +5. AJAX/fetch API untuk pertukaran data dengan file PHP backend. +6. Laragon sebagai lingkungan localhost. +7. cPanel sebagai contoh lingkungan hosting. + +--- + +## 2. Deskripsi Umum Sistem + +### 2.1 Perspektif Produk + +Aplikasi ini merupakan sistem mandiri berbasis web yang berjalan pada server PHP dan database MySQL/MariaDB. Pengguna mengakses aplikasi melalui browser. Data spasial berupa latitude dan longitude ditampilkan menggunakan Leaflet.js. + +### 2.2 Fungsi Umum Produk + +Sistem menyediakan fungsi: + +1. Autentikasi pengguna. +2. Otorisasi berdasarkan role. +3. Pengelolaan data pengguna. +4. Pengelolaan titik rumah ibadah. +5. Pengelolaan titik penerima bantuan. +6. Sinkronisasi area penerima dengan rumah ibadah. +7. Pengelolaan program bantuan. +8. Pencatatan penyaluran bantuan. +9. Pelaporan penerima sudah/belum diberi bantuan. +10. Riwayat perubahan status. + +### 2.3 Kelas Pengguna + +#### 2.3.1 Super Admin + +Super Admin adalah pengguna dengan hak akses penuh. Super Admin dapat mengelola semua akun, rumah ibadah, penerima, program bantuan, penyaluran bantuan, laporan, dan persetujuan hapus marker/pin. + +#### 2.3.2 Admin Rumah Ibadah + +Admin Rumah Ibadah adalah pengguna yang mengelola satu atau lebih rumah ibadah. Admin dapat mengelola penerima yang masuk dalam wilayah rumah ibadahnya, membuat program bantuan, mencatat penyaluran, dan melihat laporan sesuai data miliknya. + +#### 2.3.3 Penerima + +Penerima adalah pengguna yang dapat registrasi, login, mengajukan pin lokasi rumah, melihat status pengajuan, melihat riwayat bantuan, dan mengajukan hapus pin miliknya. + +### 2.4 Batasan Sistem + +1. Sistem menggunakan PHP Native tanpa framework. +2. Sistem menggunakan MySQL/MariaDB. +3. Sistem menggunakan Leaflet CDN sehingga peta membutuhkan koneksi internet. +4. Radius tanggung jawab rumah ibadah ditetapkan 300 meter. +5. Sistem tidak memakai API geocoding berbayar. +6. Sistem tidak menyediakan upload dokumen identitas. +7. Sistem tidak menyediakan notifikasi email/WhatsApp otomatis. +8. Sistem tidak menyediakan pencetakan laporan PDF otomatis. + +### 2.5 Asumsi dan Ketergantungan + +1. Browser pengguna mendukung JavaScript. +2. Server mendukung PHP dan MySQL/MariaDB. +3. Database sudah diimport sebelum aplikasi dijalankan. +4. File `koneksi.php` sudah disesuaikan dengan konfigurasi database. +5. Leaflet CDN dapat diakses dari jaringan pengguna. + +--- + +## 3. Aktor Sistem + +| Kode | Aktor | Deskripsi | +|---|---|---| +| A-01 | Super Admin | Pengelola utama sistem. | +| A-02 | Admin Rumah Ibadah | Pengelola rumah ibadah dan penyaluran bantuan. | +| A-03 | Penerima | Pengguna yang mengajukan dan menerima bantuan. | + +--- + +## 4. Kebutuhan Fungsional + +### 4.1 Autentikasi dan Akun + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-01 | Sistem harus menyediakan login menggunakan username dan password. | Semua aktor | Tinggi | +| FR-02 | Sistem harus menolak login akun yang tidak aktif. | Semua aktor | Tinggi | +| FR-03 | Sistem harus menyediakan logout. | Semua aktor | Tinggi | +| FR-04 | Sistem harus menyimpan password menggunakan hash. | Sistem | Tinggi | +| FR-05 | Sistem harus menyediakan registrasi akun penerima. | Penerima | Tinggi | +| FR-06 | Sistem harus mengizinkan super admin membuat akun admin rumah ibadah dan penerima. | Super Admin | Tinggi | +| FR-07 | Sistem harus mengizinkan admin rumah ibadah membuat akun penerima. | Admin Rumah Ibadah | Sedang | +| FR-08 | Sistem harus menyediakan fitur edit akun. | Super Admin, Admin Rumah Ibadah | Sedang | +| FR-09 | Sistem harus menonaktifkan akun yang sudah punya relasi data jika tidak dapat dihapus permanen. | Super Admin, Admin Rumah Ibadah | Tinggi | + +### 4.2 Peta WebGIS + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-10 | Sistem harus menampilkan peta menggunakan Leaflet. | Semua aktor | Tinggi | +| FR-11 | Sistem harus menampilkan marker rumah ibadah sesuai hak akses. | Semua aktor | Tinggi | +| FR-12 | Sistem harus menampilkan pin penerima sesuai hak akses. | Semua aktor | Tinggi | +| FR-13 | Sistem harus menampilkan lingkaran radius 300 meter untuk rumah ibadah aktif. | Semua aktor | Tinggi | +| FR-14 | Sistem harus menyediakan popup detail pada marker/pin. | Semua aktor | Sedang | +| FR-15 | Sistem harus memungkinkan input lokasi berdasarkan klik pada peta. | Semua aktor sesuai role | Tinggi | + +### 4.3 Pengelolaan Rumah Ibadah + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-16 | Sistem harus mengizinkan super admin menambah rumah ibadah. | Super Admin | Tinggi | +| FR-17 | Sistem harus mengizinkan admin rumah ibadah mengajukan rumah ibadah. | Admin Rumah Ibadah | Tinggi | +| FR-18 | Sistem harus menyediakan status rumah ibadah: menunggu, diterima, ditolak. | Sistem | Tinggi | +| FR-19 | Sistem harus mengizinkan super admin menyetujui/menolak rumah ibadah. | Super Admin | Tinggi | +| FR-20 | Sistem harus menghubungkan rumah ibadah dengan akun admin rumah ibadah. | Super Admin | Tinggi | +| FR-21 | Sistem harus mendukung satu admin mengelola lebih dari satu rumah ibadah. | Admin Rumah Ibadah | Tinggi | +| FR-22 | Sistem harus melepas atau menyinkronkan ulang penerima jika rumah ibadah ditolak atau dihapus. | Sistem | Tinggi | + +### 4.4 Pengelolaan Penerima Bantuan + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-23 | Sistem harus mengizinkan penerima mengajukan lokasi dirinya sendiri. | Penerima | Tinggi | +| FR-24 | Sistem harus mengizinkan super admin/admin rumah ibadah menambahkan penerima berdasarkan username penerima. | Super Admin, Admin Rumah Ibadah | Tinggi | +| FR-25 | Sistem harus menyimpan data nama, NIK, nomor HP, alamat, pekerjaan, penghasilan, tanggungan, kondisi rumah, jenis bantuan, latitude, dan longitude. | Sistem | Tinggi | +| FR-26 | Sistem harus menyediakan status penerima: menunggu, diterima, ditolak, dibatalkan. | Sistem | Tinggi | +| FR-27 | Sistem harus mengizinkan super admin menyetujui, menolak, mereset, atau membatalkan penerima. | Super Admin | Tinggi | +| FR-28 | Sistem harus mengizinkan admin rumah ibadah menyetujui/menolak penerima dalam wilayahnya. | Admin Rumah Ibadah | Tinggi | +| FR-29 | Sistem harus mencegah admin rumah ibadah mengembalikan status ke menunggu. | Admin Rumah Ibadah | Tinggi | +| FR-30 | Sistem harus mencegah admin rumah ibadah membatalkan penerima yang sudah diterima. | Admin Rumah Ibadah | Tinggi | +| FR-31 | Sistem harus membatasi satu akun penerima hanya memiliki satu pin aktif. | Sistem | Tinggi | + +### 4.5 Sinkronisasi Radius + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-32 | Sistem harus menghitung jarak penerima ke rumah ibadah menggunakan koordinat. | Sistem | Tinggi | +| FR-33 | Sistem harus mengaitkan penerima ke rumah ibadah terdekat dalam radius 300 meter. | Sistem | Tinggi | +| FR-34 | Sistem harus membiarkan penerima belum terhubung jika tidak ada rumah ibadah dalam radius 300 meter. | Sistem | Sedang | +| FR-35 | Sistem harus menyinkronkan ulang penerima saat rumah ibadah disetujui. | Sistem | Tinggi | +| FR-36 | Sistem harus menyinkronkan ulang penerima saat lokasi penerima berubah. | Sistem | Tinggi | +| FR-37 | Sistem harus memilih rumah ibadah paling dekat jika radius beberapa rumah ibadah saling beririsan. | Sistem | Tinggi | + +### 4.6 Pengajuan dan Persetujuan Hapus Marker/Pin + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-38 | Sistem harus mengizinkan super admin menghapus marker rumah ibadah secara langsung dari peta aktif. | Super Admin | Tinggi | +| FR-39 | Sistem harus mengizinkan super admin menghapus pin penerima secara langsung dari peta aktif. | Super Admin | Tinggi | +| FR-40 | Sistem harus mengizinkan admin rumah ibadah mengajukan hapus rumah ibadah miliknya. | Admin Rumah Ibadah | Sedang | +| FR-41 | Sistem harus mengizinkan admin rumah ibadah mengajukan hapus pin penerima yang dikelola. | Admin Rumah Ibadah | Sedang | +| FR-42 | Sistem harus mengizinkan penerima mengajukan hapus pin miliknya sendiri. | Penerima | Sedang | +| FR-43 | Sistem harus menyediakan panel persetujuan hapus marker/pin untuk super admin. | Super Admin | Tinggi | +| FR-44 | Sistem harus menyembunyikan marker/pin yang sudah disetujui penghapusannya. | Sistem | Tinggi | + +### 4.7 Program Bantuan + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-45 | Sistem harus mengizinkan super admin/admin rumah ibadah membuat program bantuan. | Super Admin, Admin Rumah Ibadah | Tinggi | +| FR-46 | Program bantuan harus memiliki rumah ibadah, nama program, jenis bantuan, bulan, tahun, target frekuensi, nominal/item, deskripsi, dan status. | Sistem | Tinggi | +| FR-47 | Sistem harus mengizinkan penghapusan/pembatalan program bantuan. | Super Admin, Admin Rumah Ibadah | Sedang | +| FR-48 | Sistem harus mencegah penyaluran baru pada program berstatus selesai atau dibatalkan. | Sistem | Tinggi | +| FR-49 | Sistem harus menampilkan daftar program bantuan sesuai hak akses. | Super Admin, Admin Rumah Ibadah, Penerima | Sedang | + +### 4.8 Penyaluran Bantuan + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-50 | Sistem harus mengizinkan super admin/admin rumah ibadah mencatat penyaluran bantuan. | Super Admin, Admin Rumah Ibadah | Tinggi | +| FR-51 | Sistem harus memastikan program dan penerima berada pada rumah ibadah yang sama. | Sistem | Tinggi | +| FR-52 | Sistem harus memastikan penerima berstatus diterima sebelum bantuan disalurkan. | Sistem | Tinggi | +| FR-53 | Sistem harus memastikan penerima masih berada dalam radius 300 meter rumah ibadah program. | Sistem | Tinggi | +| FR-54 | Sistem harus mengambil jenis bantuan, nominal, dan jumlah item dari program. | Sistem | Sedang | +| FR-55 | Sistem harus menghitung bantuan_ke secara otomatis. | Sistem | Tinggi | +| FR-56 | Sistem harus memberi peringatan jika penyaluran melebihi target frekuensi program. | Sistem | Sedang | + +### 4.9 Laporan dan Riwayat + +| Kode | Kebutuhan | Aktor | Prioritas | +|---|---|---|---| +| FR-57 | Sistem harus menyediakan laporan penyaluran berdasarkan bulan dan tahun. | Semua aktor sesuai hak akses | Tinggi | +| FR-58 | Sistem harus menyediakan filter rumah ibadah, agama, program, jenis bantuan, dan status penyaluran. | Semua aktor sesuai hak akses | Sedang | +| FR-59 | Sistem harus menampilkan status belum_diberi, sudah_diberi, belum_memenuhi_target, sesuai_target, dan melebihi_target. | Sistem | Tinggi | +| FR-60 | Sistem harus menyediakan rekap laporan. | Semua aktor sesuai hak akses | Sedang | +| FR-61 | Sistem harus menyimpan riwayat perubahan status penerima. | Sistem | Tinggi | +| FR-62 | Sistem harus menampilkan riwayat pengajuan, status, dan penyaluran sesuai hak akses. | Semua aktor | Sedang | + +--- + +## 5. Kebutuhan Non-Fungsional + +| Kode | Kebutuhan | Keterangan | Prioritas | +|---|---|---|---| +| NFR-01 | Keamanan Password | Password harus disimpan dengan `password_hash()` dan diverifikasi dengan `password_verify()`. | Tinggi | +| NFR-02 | Otorisasi Role | Akses fitur harus dibatasi berdasarkan role. | Tinggi | +| NFR-03 | Validasi Input | Input wajib divalidasi di sisi server. | Tinggi | +| NFR-04 | Prepared Statement | Query yang menerima input pengguna harus memakai prepared statement. | Tinggi | +| NFR-05 | Kompatibilitas Browser | Sistem dapat berjalan pada browser modern seperti Chrome, Edge, dan Firefox. | Sedang | +| NFR-06 | Responsivitas | Tampilan harus dapat digunakan pada laptop dan perangkat mobile. | Sedang | +| NFR-07 | Ketersediaan | Sistem dapat berjalan pada server PHP dan MySQL/MariaDB standar. | Tinggi | +| NFR-08 | Maintainability | Struktur file sederhana dan mudah dipelihara. | Sedang | +| NFR-09 | Usability | Antarmuka harus mudah dipahami oleh admin dan penerima. | Sedang | +| NFR-10 | Auditability | Perubahan status penting harus tercatat dalam riwayat. | Tinggi | + +--- + +## 6. Kebutuhan Data + +### 6.1 Tabel `users` + +Menyimpan data akun pengguna. + +Kolom utama: + +- id +- nama +- username +- password +- role +- status_akun +- created_at +- updated_at + +Role yang tersedia: + +- super_admin +- admin_rumah_ibadah +- penerima + +Status akun: + +- aktif +- nonaktif + +### 6.2 Tabel `rumah_ibadah` + +Menyimpan data rumah ibadah. + +Kolom utama: + +- id +- user_id +- nama_rumah_ibadah +- agama +- alamat +- nomor_hp +- latitude +- longitude +- status_pengajuan +- status_hapus +- alasan_hapus +- keterangan +- created_at +- updated_at + +Status pengajuan: + +- menunggu +- diterima +- ditolak + +Status hapus: + +- normal +- menunggu +- disetujui +- ditolak + +### 6.3 Tabel `penerima_bantuan` + +Menyimpan data penerima bantuan. + +Kolom utama: + +- id +- user_id +- rumah_ibadah_id +- nama_penerima +- nik +- no_hp +- alamat +- pekerjaan +- penghasilan +- jumlah_tanggungan +- kondisi_rumah +- jenis_bantuan +- latitude +- longitude +- jarak_meter +- status_pengajuan +- status_hapus +- pin_aktif_user_id +- alasan_hapus +- alasan_status +- sumber_pengajuan +- created_at +- updated_at + +Status pengajuan: + +- menunggu +- diterima +- ditolak +- dibatalkan + +Aturan penting: + +- `pin_aktif_user_id` digunakan untuk mencegah satu akun penerima memiliki lebih dari satu pin aktif. + +### 6.4 Tabel `program_bantuan` + +Menyimpan program bantuan. + +Kolom utama: + +- id +- rumah_ibadah_id +- nama_program +- jenis_bantuan +- deskripsi +- bulan +- tahun +- target_frekuensi +- nominal +- jumlah_item +- status_program +- created_at +- updated_at + +Status program: + +- aktif +- selesai +- dibatalkan + +### 6.5 Tabel `penyaluran_bantuan` + +Menyimpan catatan penyaluran bantuan. + +Kolom utama: + +- id +- program_id +- penerima_id +- rumah_ibadah_id +- admin_id +- jenis_bantuan +- nominal +- jumlah_item +- bantuan_ke +- keterangan +- tanggal_penyaluran +- created_at + +### 6.6 Tabel `riwayat_status` + +Menyimpan riwayat perubahan status penerima. + +Kolom utama: + +- id +- penerima_id +- user_id +- status_lama +- status_baru +- catatan +- created_at + +--- + +## 7. Aturan Bisnis + +| Kode | Aturan Bisnis | +|---|---| +| BR-01 | Setiap pengguna harus login untuk mengakses sistem. | +| BR-02 | Akun nonaktif tidak boleh login. | +| BR-03 | Registrasi umum hanya untuk role penerima. | +| BR-04 | Admin rumah ibadah hanya boleh membuat akun penerima. | +| BR-05 | Super admin dapat membuat akun admin rumah ibadah dan penerima. | +| BR-06 | Satu akun penerima hanya boleh memiliki satu pin aktif. | +| BR-07 | Penerima dikaitkan ke rumah ibadah terdekat dalam radius 300 meter. | +| BR-08 | Jika tidak ada rumah ibadah dalam radius 300 meter, penerima belum memiliki rumah ibadah peninjau. | +| BR-09 | Admin rumah ibadah hanya boleh mengelola rumah ibadah miliknya. | +| BR-10 | Admin rumah ibadah tidak boleh membatalkan penerima yang sudah diterima. | +| BR-11 | Penghapusan marker/pin oleh admin rumah ibadah dan penerima harus disetujui super admin. | +| BR-12 | Marker/pin yang status hapusnya disetujui tidak tampil sebagai data aktif. | +| BR-13 | Program dan penerima harus berada pada rumah ibadah yang sama agar penyaluran dapat dicatat. | +| BR-14 | Program berstatus selesai atau dibatalkan tidak dapat menerima penyaluran baru. | +| BR-15 | Target frekuensi menjadi acuan laporan dan peringatan, bukan penghapus data riwayat. | +| BR-16 | Data yang sudah punya relasi tidak boleh dihapus sembarangan agar audit tetap aman. | + +--- + +## 8. Use Case Sistem + +### 8.1 Daftar Use Case + +| Kode | Use Case | Aktor | +|---|---|---| +| UC-01 | Login | Semua aktor | +| UC-02 | Logout | Semua aktor | +| UC-03 | Registrasi Akun Penerima | Penerima | +| UC-04 | Kelola Akun | Super Admin, Admin Rumah Ibadah | +| UC-05 | Lihat Peta | Semua aktor | +| UC-06 | Tambah/Ajukan Rumah Ibadah | Super Admin, Admin Rumah Ibadah | +| UC-07 | Setujui/Tolak Rumah Ibadah | Super Admin | +| UC-08 | Tambah/Ajukan Penerima | Semua aktor sesuai hak akses | +| UC-09 | Setujui/Tolak Penerima | Super Admin, Admin Rumah Ibadah | +| UC-10 | Ajukan Hapus Marker/Pin | Admin Rumah Ibadah, Penerima | +| UC-11 | Setujui/Tolak Hapus Marker/Pin | Super Admin | +| UC-12 | Buat Program Bantuan | Super Admin, Admin Rumah Ibadah | +| UC-13 | Hapus/Batalkan Program Bantuan | Super Admin, Admin Rumah Ibadah | +| UC-14 | Catat Penyaluran Bantuan | Super Admin, Admin Rumah Ibadah | +| UC-15 | Lihat Laporan Penyaluran | Semua aktor sesuai hak akses | +| UC-16 | Lihat Riwayat | Semua aktor sesuai hak akses | + +### 8.2 Deskripsi Use Case Utama + +#### UC-01 Login + +Aktor memasukkan username dan password. Sistem memeriksa akun pada tabel `users`. Jika akun aktif dan password benar, sistem membuat session dan mengarahkan pengguna ke dashboard. + +#### UC-04 Kelola Akun + +Super Admin dapat membuat, mengedit, menonaktifkan, dan menghapus akun admin rumah ibadah atau penerima. Admin Rumah Ibadah hanya dapat mengelola akun penerima. + +#### UC-06 Tambah/Ajukan Rumah Ibadah + +Super Admin atau Admin Rumah Ibadah memilih titik pada peta dan mengisi data rumah ibadah. Data disimpan dengan status pengajuan sesuai role. Super Admin dapat menyetujui data agar rumah ibadah aktif. + +#### UC-08 Tambah/Ajukan Penerima + +Pengguna memilih lokasi rumah penerima pada peta dan mengisi data penerima. Sistem mengecek satu pin aktif dan menghitung rumah ibadah terdekat dalam radius 300 meter. + +#### UC-14 Catat Penyaluran Bantuan + +Admin memilih program dan penerima yang sudah diterima. Sistem memastikan program dan penerima berada pada rumah ibadah yang sama, lalu menyimpan catatan penyaluran. + +--- + +## 9. Matriks Hak Akses + +| Fitur | Super Admin | Admin Rumah Ibadah | Penerima | +|---|---:|---:|---:| +| Login | Ya | Ya | Ya | +| Registrasi akun sendiri | Tidak | Tidak | Ya | +| Kelola akun admin rumah ibadah | Ya | Tidak | Tidak | +| Kelola akun penerima | Ya | Ya | Tidak | +| Lihat peta | Ya | Ya | Ya | +| Tambah rumah ibadah | Ya | Ajukan | Tidak | +| Setujui rumah ibadah | Ya | Tidak | Tidak | +| Tambah penerima | Ya | Ya, sesuai radius | Ajukan diri sendiri | +| Setujui penerima | Ya | Ya, sesuai wilayah | Tidak | +| Reset/batalkan penerima diterima | Ya | Tidak | Tidak | +| Buat program bantuan | Ya | Ya, miliknya | Tidak | +| Catat penyaluran bantuan | Ya | Ya, miliknya | Tidak | +| Lihat laporan | Ya, semua | Ya, miliknya | Ya, miliknya | +| Hapus marker/pin langsung | Ya | Tidak | Tidak | +| Ajukan hapus marker/pin | Tidak perlu | Ya | Ya, pin sendiri | +| Setujui hapus marker/pin | Ya | Tidak | Tidak | + +--- + +## 10. Antarmuka Sistem + +### 10.1 Halaman Login + +Komponen: + +- Input username. +- Input password. +- Tombol Login. +- Link Registrasi Penerima. +- Informasi akun demo. + +### 10.2 Halaman Register + +Komponen: + +- Nama lengkap. +- Username. +- Password. +- Konfirmasi password. +- Tombol Daftar. + +### 10.3 Halaman Peta + +Komponen: + +- Peta Leaflet. +- Marker rumah ibadah. +- Pin penerima. +- Popup detail. +- Form tambah/ajukan rumah ibadah. +- Form tambah/ajukan penerima. +- Tombol setujui/tolak. +- Tombol hapus/ajukan hapus. + +### 10.4 Halaman Dashboard + +Komponen: + +- Statistik data. +- Panel persetujuan hapus marker/pin. +- Form kelola akun. +- Daftar akun. +- Monitoring penyaluran per periode. +- Rekap penyaluran. +- Form program bantuan. +- Daftar program bantuan. +- Form penyaluran bantuan. +- Tabel laporan penerima. +- Riwayat pengajuan dan bantuan. + +--- + +## 11. Kebutuhan Lingkungan + +### 11.1 Localhost + +| Komponen | Kebutuhan | +|---|---| +| Web server | Apache dari Laragon | +| Database | MySQL/MariaDB | +| PHP | PHP versi yang mendukung MySQLi dan password_hash | +| Browser | Chrome, Edge, Firefox, atau browser modern lain | +| Internet | Dibutuhkan untuk Leaflet CDN | + +### 11.2 Hosting + +| Komponen | Kebutuhan | +|---|---| +| Hosting | cPanel atau hosting PHP sejenis | +| PHP | Mendukung MySQLi | +| Database | MySQL/MariaDB | +| SSL | Disarankan menggunakan HTTPS | +| File Manager/FTP | Untuk upload file aplikasi | + +--- + +## 12. Kriteria Penerimaan Sistem + +Sistem dianggap berhasil jika: + +1. Pengguna dapat login sesuai role. +2. Akun nonaktif tidak dapat login. +3. Super admin dapat mengelola akun. +4. Admin rumah ibadah dapat mengelola akun penerima. +5. Peta dapat menampilkan rumah ibadah dan penerima. +6. Rumah ibadah dapat diajukan dan disetujui. +7. Penerima dapat diajukan dan disetujui. +8. Sistem mencegah satu akun penerima memiliki lebih dari satu pin aktif. +9. Sistem menghubungkan penerima ke rumah ibadah terdekat dalam radius 300 meter. +10. Program bantuan dapat dibuat. +11. Penyaluran bantuan dapat dicatat. +12. Laporan bulanan dapat menampilkan status bantuan. +13. Riwayat perubahan status tersimpan. +14. Pengajuan hapus marker/pin dapat diproses oleh super admin. +15. Aplikasi dapat berjalan di localhost dan hosting. + +--- + +## 13. Risiko dan Mitigasi + +| Risiko | Dampak | Mitigasi | +|---|---|---| +| File SQL gagal diimport | Sistem tidak dapat berjalan | Sediakan panduan import dan troubleshooting. | +| Password akun salah | Pengguna tidak bisa login | Gunakan akun demo dengan password `password`. | +| Leaflet CDN tidak bisa diakses | Peta tidak muncul | Pastikan koneksi internet aktif atau siapkan Leaflet lokal jika diperlukan. | +| Data penerima ganda | Laporan tidak akurat | Terapkan aturan satu pin aktif per akun penerima. | +| Admin salah mengelola data RI lain | Data tidak valid | Terapkan pembatasan akses berdasarkan user_id rumah ibadah. | +| Hapus data merusak audit | Riwayat hilang | Gunakan status hapus/nonaktif, bukan hapus permanen untuk data berelasi. | + +--- + +## 14. Penutup + +Dokumen SKPL/SRS ini menjadi acuan kebutuhan aplikasi WebGIS Poverty Mapping Project 05. Sistem dirancang untuk membantu pendataan penerima bantuan berbasis lokasi, pengelolaan rumah ibadah, pencatatan program bantuan, penyaluran bantuan, dan pelaporan penerima yang sudah atau belum menerima bantuan. diff --git a/05/TUTORIAL_CARA_PAKAI_APLIKASI_PROJECT_05.txt b/05/TUTORIAL_CARA_PAKAI_APLIKASI_PROJECT_05.txt new file mode 100644 index 0000000..cdf3dbf --- /dev/null +++ b/05/TUTORIAL_CARA_PAKAI_APLIKASI_PROJECT_05.txt @@ -0,0 +1,510 @@ +TUTORIAL CARA PAKAI APLIKASI WEBGIS POVERTY MAPPING PROJECT 05 +================================================================= + +A. AKUN LOGIN DEMO +------------------ + +Semua password akun demo adalah: + +password + +Akun utama: + +1. Super Admin + Username : superadmin + Password : password + +Contoh akun admin rumah ibadah jika memakai database demo: + +2. Admin Gereja Sesilia + Username : admingerejasesilia + Password : password + +3. Admin Gereja Elim + Username : admingerejaelim + Password : password + +Contoh akun penerima jika memakai database demo: + +4. Penerima 02 + Username : penerima02 + Password : password + +5. Penerima 04 + Username : penerima04 + Password : password + +6. Penerima 05 + Username : penerima05 + Password : password + +Catatan: +- Akun nonaktif tidak bisa login. +- Jika membuat akun baru, password minimal 6 karakter. + +================================================================= + +B. ALUR UMUM APLIKASI +--------------------- + +Aplikasi ini dipakai untuk: + +1. Memetakan rumah ibadah. +2. Memetakan calon penerima bantuan. +3. Menghubungkan penerima dengan rumah ibadah terdekat dalam radius 300 meter. +4. Menyetujui atau menolak pengajuan penerima. +5. Membuat program bantuan. +6. Mencatat penyaluran bantuan. +7. Melihat laporan penerima yang sudah/belum diberi bantuan per bulan. + +Role pengguna: + +1. Super Admin + Mengelola semua data. + +2. Admin Rumah Ibadah + Mengelola rumah ibadah, penerima, program, dan penyaluran sesuai rumah ibadahnya. + +3. Penerima + Mengajukan titik lokasi rumah dan melihat riwayat bantuan. + +================================================================= + +C. CARA LOGIN +------------- + +1. Buka aplikasi: + http://localhost/webgis-poverty/05/ + +2. Masukkan username. + +3. Masukkan password. + +4. Klik Login. + +5. Setelah berhasil, pengguna masuk ke dashboard. + +================================================================= + +D. CARA LOGOUT +-------------- + +1. Klik tombol Logout di bagian atas halaman. +2. Sistem akan keluar dari akun. +3. Pengguna diarahkan kembali ke halaman login. + +================================================================= + +E. CARA PAKAI SEBAGAI SUPER ADMIN +--------------------------------- + +Super Admin adalah role tertinggi. +Super Admin dapat mengelola seluruh data aplikasi. + +1. Login Super Admin + + Username : superadmin + Password : password + +2. Melihat Dashboard + + Setelah login, dashboard menampilkan statistik seperti: + - Rumah ibadah menunggu. + - Rumah ibadah diterima. + - Penerima menunggu. + - Penerima diterima. + - Penerima ditolak. + - Program aktif. + +3. Mengelola Akun + + Langkah: + - Buka Dashboard. + - Cari bagian Kelola Akun Pengguna. + - Isi nama, username, password, role, dan status akun. + - Pilih role: Admin Rumah Ibadah atau Penerima. + - Klik Buat Akun. + + Super Admin dapat: + - membuat akun admin rumah ibadah; + - membuat akun penerima; + - mengedit akun; + - menonaktifkan akun; + - menghapus akun jika belum punya relasi data. + + Jika akun sudah punya relasi data, sistem tidak menghapus permanen, tetapi menonaktifkan akun agar data riwayat tetap aman. + +4. Menambah Rumah Ibadah + + Langkah: + - Klik Buka Peta. + - Klik titik lokasi rumah ibadah di peta. + - Klik Tambah Rumah Ibadah. + - Isi username admin rumah ibadah. + - Isi nama rumah ibadah. + - Pilih agama. + - Isi alamat dan nomor HP. + - Klik Ajukan/Tambah. + + Jika Super Admin yang menambah, data dapat langsung aktif/diterima. + +5. Menyetujui Rumah Ibadah + + Langkah: + - Buka peta. + - Klik marker rumah ibadah yang berstatus menunggu. + - Klik Setujui jika data benar. + - Klik Tolak jika data tidak valid. + + Jika rumah ibadah disetujui, sistem akan menghitung ulang penerima yang berada dalam radius 300 meter. + +6. Menambah Penerima + + Langkah: + - Buka peta. + - Klik lokasi rumah penerima. + - Klik Tambah Penerima. + - Isi username akun penerima. + - Isi nama, NIK, nomor HP, alamat, pekerjaan, penghasilan, jumlah tanggungan, kondisi rumah, dan jenis bantuan. + - Klik Simpan. + + Sistem akan mencari rumah ibadah terdekat dalam radius 300 meter. + +7. Menyetujui Penerima + + Langkah: + - Buka peta. + - Klik marker penerima berstatus menunggu. + - Klik Setujui untuk menerima. + - Klik Tolak untuk menolak. + + Penerima yang diterima dapat masuk ke laporan dan penyaluran bantuan. + +8. Menghapus Marker/Pin + + Super Admin dapat langsung menghapus marker rumah ibadah atau pin penerima dari peta aktif. + + Langkah: + - Klik marker/pin. + - Klik Hapus Marker atau Hapus Pin. + - Isi alasan jika diminta. + - Konfirmasi. + + Data tidak hilang permanen dari database, tetapi tidak tampil lagi sebagai data aktif. + +9. Memproses Pengajuan Hapus Marker/Pin + + Langkah: + - Buka Dashboard. + - Cari bagian Persetujuan Hapus Marker/Pin. + - Lihat pengajuan hapus rumah ibadah atau penerima. + - Klik Setujui Hapus atau Tolak Hapus. + +10. Membuat Program Bantuan + + Langkah: + - Buka Dashboard. + - Cari bagian Buat Program Bantuan. + - Pilih rumah ibadah. + - Isi nama program. + - Pilih jenis bantuan. + - Isi bulan dan tahun. + - Isi target frekuensi. + - Isi nominal atau jumlah item jika ada. + - Isi deskripsi. + - Klik Simpan. + +11. Menghapus Program Bantuan + + Langkah: + - Buka bagian Daftar Program. + - Pilih program. + - Klik Hapus. + + Jika program sudah punya data penyaluran, sistem dapat membatalkan program, bukan menghapus permanen. + +12. Mencatat Penyaluran Bantuan + + Langkah: + - Buka Dashboard. + - Cari bagian Catat Penyaluran Bantuan. + - Pilih program. + - Pilih penerima diterima. + - Jenis bantuan, nominal, dan item akan mengikuti program. + - Isi tanggal. + - Isi keterangan jika perlu. + - Klik Simpan. + + Syarat: + - Penerima harus berstatus diterima. + - Penerima harus berada pada rumah ibadah yang sama dengan program. + - Penerima harus berada dalam radius 300 meter. + - Program tidak boleh berstatus selesai atau dibatalkan. + +13. Melihat Laporan Bulanan + + Langkah: + - Buka Dashboard. + - Cari bagian Monitoring Penyaluran Per Periode. + - Pilih bulan dan tahun. + - Gunakan filter rumah ibadah, agama, program, jenis bantuan, dan status. + - Klik tampilkan/muat laporan. + + Status laporan: + - belum_diberi + - sudah_diberi + - belum_memenuhi_target + - sesuai_target + - melebihi_target + +14. Melihat Riwayat + + Langkah: + - Buka Dashboard. + - Cari bagian Riwayat Pengajuan dan Bantuan. + - Lihat daftar pengajuan, perubahan status, dan penyaluran. + +================================================================= + +F. CARA PAKAI SEBAGAI ADMIN RUMAH IBADAH +---------------------------------------- + +Admin Rumah Ibadah mengelola data sesuai rumah ibadah miliknya. +Satu admin dapat mengelola lebih dari satu rumah ibadah. + +1. Login Admin Rumah Ibadah + + Contoh: + Username : admingerejasesilia + Password : password + +2. Mengajukan Rumah Ibadah + + Langkah: + - Buka peta. + - Klik lokasi rumah ibadah. + - Klik Ajukan Rumah Ibadah. + - Isi nama rumah ibadah, agama, alamat, dan nomor HP. + - Klik Ajukan. + + Status awal adalah menunggu. + Super Admin harus menyetujui pengajuan tersebut. + +3. Membuat Akun Penerima + + Langkah: + - Buka Dashboard. + - Cari Kelola Akun Pengguna. + - Isi nama, username, dan password penerima. + - Role otomatis/terbatas sebagai penerima. + - Klik Buat Akun. + +4. Menambah Penerima + + Langkah: + - Buka peta. + - Klik lokasi rumah penerima. + - Klik Tambah Penerima. + - Isi username penerima. + - Isi data penerima. + - Klik Simpan. + + Catatan: + - Penerima harus berada dalam radius 300 meter dari rumah ibadah milik admin. + - Jika lokasi lebih dekat ke rumah ibadah lain, sistem akan menolak agar data tidak salah sinkron. + +5. Menyetujui atau Menolak Penerima + + Langkah: + - Buka peta. + - Klik marker penerima menunggu. + - Klik Setujui atau Tolak. + + Batasan: + - Admin rumah ibadah tidak boleh mengembalikan status penerima ke menunggu. + - Admin rumah ibadah tidak boleh membatalkan penerima yang sudah diterima. + - Untuk reset/batal penerima diterima, hubungi Super Admin. + +6. Membuat Program Bantuan + + Langkah: + - Buka Dashboard. + - Pilih rumah ibadah milik admin. + - Isi nama program, jenis bantuan, bulan, tahun, target, nominal/item, dan deskripsi. + - Klik Simpan. + +7. Mencatat Penyaluran Bantuan + + Langkah: + - Buka Dashboard. + - Pilih program. + - Pilih penerima yang sudah diterima. + - Isi tanggal dan keterangan. + - Klik Simpan. + + Sistem hanya menampilkan/menerima penerima yang sesuai dengan rumah ibadah program. + +8. Melihat Laporan + + Langkah: + - Buka Dashboard. + - Pilih bulan dan tahun. + - Gunakan filter yang tersedia. + - Lihat data penerima yang sudah atau belum diberi bantuan. + +9. Mengajukan Hapus Marker/Pin + + Langkah: + - Buka peta. + - Klik marker rumah ibadah milik admin atau pin penerima yang dikelola. + - Klik Ajukan Hapus. + - Isi alasan. + - Kirim. + + Super Admin akan memutuskan setuju atau tolak. + +================================================================= + +G. CARA PAKAI SEBAGAI PENERIMA +------------------------------ + +Penerima dapat membuat akun sendiri melalui registrasi. + +1. Registrasi Akun Penerima + + Langkah: + - Buka halaman login. + - Klik Registrasi. + - Isi nama lengkap. + - Isi username. + - Isi password minimal 6 karakter. + - Isi konfirmasi password. + - Klik Daftar. + +2. Login Penerima + + Langkah: + - Buka halaman login. + - Masukkan username dan password. + - Klik Login. + +3. Mengajukan Titik Lokasi Rumah + + Langkah: + - Buka peta. + - Klik lokasi rumah pada peta. + - Isi data penerima. + - Klik Simpan Pengajuan. + + Sistem akan memeriksa apakah lokasi masuk radius 300 meter dari rumah ibadah diterima. + +4. Melihat Status Pengajuan + + Langkah: + - Buka peta atau dashboard. + - Lihat status pengajuan. + + Status yang mungkin: + - menunggu + - diterima + - ditolak + - dibatalkan + +5. Melihat Riwayat Bantuan + + Langkah: + - Buka Dashboard. + - Lihat bagian riwayat atau laporan yang tersedia untuk akun penerima. + +6. Mengajukan Hapus Pin + + Langkah: + - Buka peta. + - Klik pin milik sendiri. + - Klik Ajukan Hapus Pin. + - Isi alasan. + - Kirim. + + Pengajuan hapus harus disetujui Super Admin. + +================================================================= + +H. ATURAN PENTING SISTEM +------------------------ + +1. Satu akun penerima hanya boleh punya satu pin aktif. + +2. Rumah ibadah aktif adalah rumah ibadah yang statusnya diterima dan tidak dihapus. + +3. Penerima aktif adalah penerima yang statusnya menunggu/diterima dan tidak dihapus. + +4. Radius tanggung jawab rumah ibadah adalah 300 meter. + +5. Jika ada beberapa rumah ibadah dalam radius 300 meter, penerima dikaitkan ke rumah ibadah terdekat. + +6. Admin rumah ibadah hanya boleh mengelola data rumah ibadahnya sendiri. + +7. Super Admin memiliki akses paling luas. + +8. Password akun demo adalah password. + +9. File SQL dan ZIP backup jangan dibiarkan di public_html setelah hosting. + +================================================================= + +I. TIPS SAAT PRESENTASI / DEMO +------------------------------ + +Urutan demo yang rapi: + +1. Login sebagai Super Admin. +2. Tunjukkan dashboard. +3. Buka peta. +4. Tunjukkan marker rumah ibadah dan penerima. +5. Tambahkan akun penerima baru. +6. Tambahkan atau ajukan rumah ibadah. +7. Tambahkan penerima dari peta. +8. Setujui penerima. +9. Buat program bantuan. +10. Catat penyaluran bantuan. +11. Buka laporan bulanan. +12. Tunjukkan status sudah/belum diberi bantuan. +13. Tunjukkan pengajuan hapus marker/pin. +14. Logout. + +================================================================= + +J. MASALAH UMUM DAN SOLUSI CEPAT +-------------------------------- + +1. Tidak bisa login + - Cek username. + - Cek password: password. + - Cek status akun harus aktif. + +2. Peta tidak muncul + - Pastikan internet aktif. + - Leaflet diambil dari CDN. + +3. Marker tidak muncul + - Cek status data. + - Data ditolak atau dihapus memang tidak tampil aktif. + +4. Penerima tidak bisa ditambah + - Cek apakah akun penerima sudah punya pin aktif. + - Cek apakah koordinat valid. + - Cek apakah username penerima benar dan aktif. + +5. Admin rumah ibadah tidak bisa menambah penerima + - Cek apakah lokasi penerima masuk radius 300 meter rumah ibadah miliknya. + - Cek apakah penerima lebih dekat ke rumah ibadah lain. + +6. Tidak bisa mencatat bantuan + - Cek status program. + - Cek status penerima. + - Cek apakah program dan penerima berada pada rumah ibadah yang sama. + +================================================================= + +SELESAI +================================================================= diff --git a/05/ajukan_rumah_ibadah.php b/05/ajukan_rumah_ibadah.php new file mode 100644 index 0000000..5223a6e --- /dev/null +++ b/05/ajukan_rumah_ibadah.php @@ -0,0 +1,49 @@ +prepare("INSERT INTO rumah_ibadah (user_id, nama_rumah_ibadah, agama, alamat, nomor_hp, latitude, longitude, status_pengajuan, keterangan) VALUES (?,?,?,?,?,?,?,?,?)"); +$stmt->bind_param('issssddss', $user_id, $nama, $agama, $alamat, $nomor_hp, $lat, $lng, $status, $ket); +if (!$stmt->execute()) json_response('err', 'Gagal mengajukan rumah ibadah: ' . $koneksi->error); +$id = $stmt->insert_id; + +$jumlahSinkron = 0; +if ($status === 'diterima') { + // Jika rumah ibadah langsung diterima, penerima lama yang berada dalam radius otomatis ditautkan. + $jumlahSinkron = sync_penerima_area_by_rumah_ibadah($id); +} + +$stmt = $koneksi->prepare("SELECT ri.*, u.nama AS nama_admin, u.username AS username_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.id=?"); +$stmt->bind_param('i', $id); +$row = fetch_all_stmt($stmt)[0] ?? null; +if ($row) $row['jumlah_penerima_tersinkron'] = $jumlahSinkron; +json_response('ok', 'Rumah ibadah berhasil diajukan.' . ($jumlahSinkron ? " $jumlahSinkron penerima lama otomatis masuk area tanggung jawab." : ''), $row); +?> diff --git a/05/ambil_data_peta.php b/05/ambil_data_peta.php new file mode 100644 index 0000000..dd57f91 --- /dev/null +++ b/05/ambil_data_peta.php @@ -0,0 +1,55 @@ + [], 'penerima' => []]; + +// Ambil rumah ibadah sesuai role. +if ($role === 'super_admin') { + $sql = "SELECT ri.*, u.nama AS nama_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.status_pengajuan IN ('diterima','menunggu') AND COALESCE(ri.status_hapus,'normal')<>'disetujui' ORDER BY ri.id DESC"; + $res = $koneksi->query($sql); + while ($row = $res->fetch_assoc()) $data['rumah_ibadah'][] = $row; +} elseif ($role === 'admin_rumah_ibadah') { + $uid = (int) $user['id']; + // Admin hanya melihat rumah ibadah miliknya, termasuk yang masih menunggu. + $stmt = $koneksi->prepare("SELECT ri.*, u.nama AS nama_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.user_id=? AND ri.status_pengajuan IN ('diterima','menunggu') AND COALESCE(ri.status_hapus,'normal')<>'disetujui' ORDER BY ri.id DESC"); + $stmt->bind_param('i', $uid); + $data['rumah_ibadah'] = fetch_all_stmt($stmt); +} else { + $res = $koneksi->query("SELECT ri.*, NULL AS nama_admin FROM rumah_ibadah ri WHERE ri.status_pengajuan='diterima' AND COALESCE(ri.status_hapus,'normal')<>'disetujui' ORDER BY ri.id DESC"); + while ($row = $res->fetch_assoc()) $data['rumah_ibadah'][] = $row; +} + +// Ambil penerima sesuai role. Marker ditolak tidak dikirim. +if ($role === 'super_admin') { + $sql = "SELECT pb.*, ri.nama_rumah_ibadah, ri.agama FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.status_pengajuan IN ('diterima','menunggu','dibatalkan') AND COALESCE(pb.status_hapus,'normal')<>'disetujui' ORDER BY pb.id DESC"; + $res = $koneksi->query($sql); + while ($row = $res->fetch_assoc()) $data['penerima'][] = $row; +} elseif ($role === 'admin_rumah_ibadah') { + $ids = get_admin_rumah_ibadah_ids((int)$user['id'], true); + if (!empty($ids)) { + $types = ''; + $params = []; + $in = build_in_condition('pb.rumah_ibadah_id', $ids, $types, $params); + $sql = "SELECT pb.*, ri.nama_rumah_ibadah, ri.agama + FROM penerima_bantuan pb + LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id + WHERE $in + AND pb.status_pengajuan IN ('diterima','menunggu') + AND COALESCE(pb.status_hapus,'normal')<>'disetujui' + ORDER BY pb.id DESC"; + $stmt = $koneksi->prepare($sql); + stmt_bind($stmt, $types, $params); + $data['penerima'] = fetch_all_stmt($stmt); + } +} else { + $uid = (int) $user['id']; + $stmt = $koneksi->prepare("SELECT pb.*, ri.nama_rumah_ibadah, ri.agama FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.user_id=? AND pb.status_pengajuan IN ('diterima','menunggu','dibatalkan') AND COALESCE(pb.status_hapus,'normal')<>'disetujui' ORDER BY pb.id DESC"); + $stmt->bind_param('i', $uid); + $data['penerima'] = fetch_all_stmt($stmt); +} + +json_response('ok', 'Data peta berhasil diambil.', $data); +?> diff --git a/05/ambil_laporan_penyaluran.php b/05/ambil_laporan_penyaluran.php new file mode 100644 index 0000000..6f524b0 --- /dev/null +++ b/05/ambil_laporan_penyaluran.php @@ -0,0 +1,138 @@ + 12 || $tahun < 2000) json_response('err', 'Filter bulan/tahun tidak valid.'); +if (!valid_enum($status_filter, ['semua','belum_diberi','sudah_diberi','belum_memenuhi_target','sesuai_target','melebihi_target'])) json_response('err', 'Status penyaluran tidak valid.'); + +$where = ["pb.status_pengajuan='diterima'", "COALESCE(pb.status_hapus,'normal')<>'disetujui'", "COALESCE(ri.status_hapus,'normal')<>'disetujui'", "prog.bulan=?", "prog.tahun=?", "prog.status_program<>'dibatalkan'"]; +$types = 'ii'; +$params = [$bulan, $tahun]; + +if ($program_filter !== '') { + $where[] = 'prog.id=?'; + $types .= 'i'; + $params[] = (int)$program_filter; +} +if ($jenis_filter !== '') { + $where[] = 'prog.jenis_bantuan=?'; + $types .= 's'; + $params[] = $jenis_filter; +} +if ($agama_filter !== '') { + $where[] = 'ri.agama=?'; + $types .= 's'; + $params[] = $agama_filter; +} +if ($rumah_filter !== '') { + $where[] = 'ri.id=?'; + $types .= 'i'; + $params[] = (int)$rumah_filter; +} + +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + $ids = get_admin_rumah_ibadah_ids((int)$_SESSION['user_id'], true); + if (empty($ids)) json_response('ok', 'Admin belum memiliki rumah ibadah diterima.', ['rows'=>[], 'rekap'=>[]]); + $where[] = build_in_condition('ri.id', $ids, $types, $params); +} elseif ($_SESSION['role'] === 'penerima') { + $where[] = 'pb.user_id=?'; + $types .= 'i'; + $params[] = (int)$_SESSION['user_id']; +} + +$whereSql = implode(' AND ', $where); + +$sql = " +SELECT + pb.id AS penerima_id, + pb.nama_penerima, + pb.no_hp, + pb.alamat, + pb.jenis_bantuan AS jenis_bantuan_diminta, + ri.id AS rumah_ibadah_id, + ri.nama_rumah_ibadah, + ri.agama, + prog.id AS program_id, + prog.nama_program, + prog.status_program, + prog.jenis_bantuan, + prog.bulan, + prog.tahun, + prog.target_frekuensi, + prog.nominal, + prog.jumlah_item, + COALESCE(cnt.jumlah_penyaluran, 0) AS jumlah_penyaluran, + cnt.tanggal_terakhir, + cnt.riwayat_penyaluran +FROM penerima_bantuan pb +JOIN rumah_ibadah ri ON ri.id = pb.rumah_ibadah_id +JOIN program_bantuan prog ON prog.rumah_ibadah_id = ri.id +LEFT JOIN ( + SELECT penerima_id, program_id, + COUNT(id) AS jumlah_penyaluran, + MAX(tanggal_penyaluran) AS tanggal_terakhir, + GROUP_CONCAT(CONCAT(tanggal_penyaluran, ' | ', jenis_bantuan, ' | Rp', COALESCE(nominal,0), ' | Item: ', COALESCE(jumlah_item,''), ' | ke-', COALESCE(bantuan_ke,0), ' | ', COALESCE(keterangan,'')) ORDER BY tanggal_penyaluran SEPARATOR ';;') AS riwayat_penyaluran + FROM penyaluran_bantuan + WHERE MONTH(tanggal_penyaluran)=? AND YEAR(tanggal_penyaluran)=? + GROUP BY penerima_id, program_id +) cnt ON cnt.penerima_id = pb.id AND cnt.program_id = prog.id +WHERE $whereSql +ORDER BY ri.nama_rumah_ibadah ASC, pb.nama_penerima ASC, prog.nama_program ASC +"; + +// Dua parameter pertama untuk subquery count bulan/tahun, lalu parameter where. +$bindTypes = 'ii' . $types; +$bindParams = array_merge([$bulan, $tahun], $params); +$stmt = $koneksi->prepare($sql); +if (!$stmt) json_response('err', 'Query laporan gagal disiapkan: ' . $koneksi->error); +stmt_bind($stmt, $bindTypes, $bindParams); +$stmt->execute(); +$res = $stmt->get_result(); + +$rows = []; +$rekap = [ + 'total' => 0, + 'belum_diberi' => 0, + 'sudah_diberi' => 0, + 'belum_memenuhi_target' => 0, + 'sesuai_target' => 0, + 'melebihi_target' => 0 +]; + +while ($row = $res->fetch_assoc()) { + $jumlah = (int)$row['jumlah_penyaluran']; + $target = max(1, (int)$row['target_frekuensi']); + if ($jumlah === 0) $status = 'belum_diberi'; + elseif ($jumlah > 0 && $jumlah < $target) $status = 'belum_memenuhi_target'; + elseif ($jumlah === $target) $status = 'sesuai_target'; + else $status = 'melebihi_target'; + + $row['status_penyaluran'] = $status; + $row['riwayat'] = $row['riwayat_penyaluran'] ? explode(';;', $row['riwayat_penyaluran']) : []; + + $include = false; + if ($status_filter === 'semua') $include = true; + elseif ($status_filter === 'sudah_diberi') $include = $jumlah > 0; + else $include = ($status_filter === $status); + + if ($include) { + $rows[] = $row; + $rekap['total']++; + if ($jumlah > 0) $rekap['sudah_diberi']++; + else $rekap['belum_diberi']++; + if ($status === 'belum_memenuhi_target') $rekap['belum_memenuhi_target']++; + if ($status === 'sesuai_target') $rekap['sesuai_target']++; + if ($status === 'melebihi_target') $rekap['melebihi_target']++; + } +} + +json_response('ok', 'Laporan penyaluran berhasil diambil.', ['rows' => $rows, 'rekap' => $rekap]); +?> diff --git a/05/ambil_program_bantuan.php b/05/ambil_program_bantuan.php new file mode 100644 index 0000000..ae6d48b --- /dev/null +++ b/05/ambil_program_bantuan.php @@ -0,0 +1,28 @@ +query($sql); + while ($row = $res->fetch_assoc()) $rows[] = $row; +} elseif ($role === 'admin_rumah_ibadah') { + $ids = get_admin_rumah_ibadah_ids((int)$_SESSION['user_id'], true); + if (!empty($ids)) { + $types = ''; + $params = []; + $in = build_in_condition('p.rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT p.*, ri.nama_rumah_ibadah FROM program_bantuan p LEFT JOIN rumah_ibadah ri ON ri.id=p.rumah_ibadah_id WHERE $in ORDER BY p.tahun DESC, p.bulan DESC, p.id DESC"); + stmt_bind($stmt, $types, $params); + $rows = fetch_all_stmt($stmt); + } +} else { + $uid = (int) $_SESSION['user_id']; + $stmt = $koneksi->prepare("SELECT DISTINCT p.*, ri.nama_rumah_ibadah FROM program_bantuan p INNER JOIN penyaluran_bantuan pb ON pb.program_id=p.id INNER JOIN penerima_bantuan pr ON pr.id=pb.penerima_id LEFT JOIN rumah_ibadah ri ON ri.id=p.rumah_ibadah_id WHERE pr.user_id=? ORDER BY p.tahun DESC, p.bulan DESC, p.id DESC"); + $stmt->bind_param('i', $uid); + $rows = fetch_all_stmt($stmt); +} +json_response('ok', 'Program bantuan berhasil diambil.', $rows); +?> diff --git a/05/ambil_riwayat.php b/05/ambil_riwayat.php new file mode 100644 index 0000000..ca27291 --- /dev/null +++ b/05/ambil_riwayat.php @@ -0,0 +1,44 @@ + [], 'status' => [], 'penyaluran' => []]; + +if ($role === 'super_admin') { + $sql = "SELECT pb.*, ri.nama_rumah_ibadah FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id ORDER BY pb.created_at DESC LIMIT 100"; + $res = $koneksi->query($sql); while($row=$res->fetch_assoc()) $data['pengajuan'][]=$row; + $sql = "SELECT rw.*, pb.nama_penerima, u.nama AS nama_user FROM riwayat_status rw JOIN penerima_bantuan pb ON pb.id=rw.penerima_id JOIN users u ON u.id=rw.user_id ORDER BY rw.created_at DESC LIMIT 100"; + $res = $koneksi->query($sql); while($row=$res->fetch_assoc()) $data['status'][]=$row; + $sql = "SELECT pn.*, pb.nama_penerima, ri.nama_rumah_ibadah, pr.nama_program FROM penyaluran_bantuan pn JOIN penerima_bantuan pb ON pb.id=pn.penerima_id JOIN rumah_ibadah ri ON ri.id=pn.rumah_ibadah_id LEFT JOIN program_bantuan pr ON pr.id=pn.program_id ORDER BY pn.tanggal_penyaluran DESC, pn.id DESC LIMIT 100"; + $res = $koneksi->query($sql); while($row=$res->fetch_assoc()) $data['penyaluran'][]=$row; +} elseif ($role === 'admin_rumah_ibadah') { + $ids = get_admin_rumah_ibadah_ids((int)$_SESSION['user_id'], true); + if (!empty($ids)) { + $types = ''; $params = []; + $inPb = build_in_condition('pb.rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT pb.*, ri.nama_rumah_ibadah FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE $inPb ORDER BY pb.created_at DESC LIMIT 100"); + stmt_bind($stmt, $types, $params); $data['pengajuan'] = fetch_all_stmt($stmt); + + $types = ''; $params = []; + $inPb = build_in_condition('pb.rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT rw.*, pb.nama_penerima, u.nama AS nama_user FROM riwayat_status rw JOIN penerima_bantuan pb ON pb.id=rw.penerima_id JOIN users u ON u.id=rw.user_id WHERE $inPb ORDER BY rw.created_at DESC LIMIT 100"); + stmt_bind($stmt, $types, $params); $data['status'] = fetch_all_stmt($stmt); + + $types = ''; $params = []; + $inPn = build_in_condition('pn.rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT pn.*, pb.nama_penerima, ri.nama_rumah_ibadah, pr.nama_program FROM penyaluran_bantuan pn JOIN penerima_bantuan pb ON pb.id=pn.penerima_id JOIN rumah_ibadah ri ON ri.id=pn.rumah_ibadah_id LEFT JOIN program_bantuan pr ON pr.id=pn.program_id WHERE $inPn ORDER BY pn.tanggal_penyaluran DESC, pn.id DESC LIMIT 100"); + stmt_bind($stmt, $types, $params); $data['penyaluran'] = fetch_all_stmt($stmt); + } +} else { + $uid = (int)$_SESSION['user_id']; + $stmt = $koneksi->prepare("SELECT pb.*, ri.nama_rumah_ibadah FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.user_id=? ORDER BY pb.created_at DESC LIMIT 100"); + $stmt->bind_param('i', $uid); $data['pengajuan'] = fetch_all_stmt($stmt); + $stmt = $koneksi->prepare("SELECT rw.*, pb.nama_penerima, u.nama AS nama_user FROM riwayat_status rw JOIN penerima_bantuan pb ON pb.id=rw.penerima_id JOIN users u ON u.id=rw.user_id WHERE pb.user_id=? ORDER BY rw.created_at DESC LIMIT 100"); + $stmt->bind_param('i', $uid); $data['status'] = fetch_all_stmt($stmt); + $stmt = $koneksi->prepare("SELECT pn.*, pb.nama_penerima, ri.nama_rumah_ibadah, pr.nama_program FROM penyaluran_bantuan pn JOIN penerima_bantuan pb ON pb.id=pn.penerima_id JOIN rumah_ibadah ri ON ri.id=pn.rumah_ibadah_id LEFT JOIN program_bantuan pr ON pr.id=pn.program_id WHERE pb.user_id=? ORDER BY pn.tanggal_penyaluran DESC, pn.id DESC LIMIT 100"); + $stmt->bind_param('i', $uid); $data['penyaluran'] = fetch_all_stmt($stmt); +} + +json_response('ok', 'Riwayat berhasil diambil.', $data); +?> diff --git a/05/ambil_user.php b/05/ambil_user.php new file mode 100644 index 0000000..f565a19 --- /dev/null +++ b/05/ambil_user.php @@ -0,0 +1,76 @@ +'disetujui' + LEFT JOIN rumah_ibadah rpb ON rpb.id=pb.rumah_ibadah_id + LEFT JOIN rumah_ibadah ri ON ri.user_id=u.id AND COALESCE(ri.status_hapus,'normal')<>'disetujui' + WHERE " . implode(' AND ', $where) . " + GROUP BY u.id + ORDER BY u.updated_at DESC, u.id DESC"; +$stmt = $koneksi->prepare($sql); +if (!$stmt) json_response('err', 'Query daftar akun gagal: ' . $koneksi->error, [], 500); +if ($types) stmt_bind($stmt, $types, $params); +$rows = fetch_all_stmt($stmt); + +json_response('ok', 'Daftar akun berhasil diambil.', $rows); +?> diff --git a/05/auth.php b/05/auth.php new file mode 100644 index 0000000..c3abdaf --- /dev/null +++ b/05/auth.php @@ -0,0 +1,314 @@ + $status, + 'message' => $message, + 'data' => $data + ], JSON_UNESCAPED_UNICODE); + exit; +} + +function require_post() { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + json_response('err', 'Method harus POST.', [], 405); + } +} + +function is_logged_in() { + return isset($_SESSION['user_id'], $_SESSION['role']); +} + +function require_login($json = false) { + if (!is_logged_in()) { + if ($json) json_response('err', 'Silakan login terlebih dahulu.', [], 401); + header('Location: login.php'); + exit; + } +} + +function current_user() { + global $koneksi; + if (!is_logged_in()) return null; + $id = (int) $_SESSION['user_id']; + $stmt = $koneksi->prepare("SELECT id, nama, username, role, status_akun FROM users WHERE id=? LIMIT 1"); + $stmt->bind_param('i', $id); + $stmt->execute(); + $res = $stmt->get_result(); + return $res->fetch_assoc(); +} + +function has_role($roles) { + if (!is_array($roles)) $roles = [$roles]; + return is_logged_in() && in_array($_SESSION['role'], $roles, true); +} + +function require_role($roles, $json = false) { + if (!has_role($roles)) { + if ($json) json_response('err', 'Akses ditolak. Role tidak sesuai.', [], 403); + http_response_code(403); + echo '

403 - Akses ditolak

'; + exit; + } +} + +function post($key, $default = '') { + return isset($_POST[$key]) ? trim((string) $_POST[$key]) : $default; +} + +function getv($key, $default = '') { + return isset($_GET[$key]) ? trim((string) $_GET[$key]) : $default; +} + +function clean_text($str, $max = 255) { + $str = trim((string) $str); + if (mb_strlen($str) > $max) $str = mb_substr($str, 0, $max); + return $str; +} + +function valid_enum($value, $allowed) { + return in_array($value, $allowed, true); +} + +function valid_latlng($lat, $lng) { + return is_numeric($lat) && is_numeric($lng) && $lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180; +} + +function haversine_meter($lat1, $lon1, $lat2, $lon2) { + $R = 6371000; // 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 * atan2(sqrt($a), sqrt(1 - $a)); + return $R * $c; +} + +function stmt_bind($stmt, $types, $params) { + if ($types === '' || empty($params)) return true; + $refs = []; + $refs[] = $types; + foreach ($params as $k => $v) { + $refs[] = &$params[$k]; + } + return call_user_func_array([$stmt, 'bind_param'], $refs); +} + +function fetch_all_stmt($stmt) { + $stmt->execute(); + $result = $stmt->get_result(); + $rows = []; + while ($row = $result->fetch_assoc()) $rows[] = $row; + return $rows; +} + +function build_in_condition($column, $values, &$types, &$params) { + $ids = []; + foreach ((array)$values as $v) { + $iv = (int)$v; + if ($iv > 0) $ids[] = $iv; + } + $ids = array_values(array_unique($ids)); + if (empty($ids)) return '1=0'; + $placeholders = implode(',', array_fill(0, count($ids), '?')); + $types .= str_repeat('i', count($ids)); + foreach ($ids as $id) $params[] = $id; + return "$column IN ($placeholders)"; +} + +function get_admin_rumah_ibadah_list($user_id = null, $only_diterima = true) { + global $koneksi; + if ($user_id === null) $user_id = (int) $_SESSION['user_id']; + $sql = "SELECT * FROM rumah_ibadah WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui'"; + if ($only_diterima) $sql .= " AND status_pengajuan='diterima'"; + $sql .= " ORDER BY id ASC"; + $stmt = $koneksi->prepare($sql); + $stmt->bind_param('i', $user_id); + return fetch_all_stmt($stmt); +} + +function get_admin_rumah_ibadah_ids($user_id = null, $only_diterima = true) { + $rows = get_admin_rumah_ibadah_list($user_id, $only_diterima); + return array_map('intval', array_column($rows, 'id')); +} + +function get_admin_rumah_ibadah($user_id = null) { + // Dipertahankan untuk kompatibilitas lama. Untuk logic baru, pakai get_admin_rumah_ibadah_list()/ids(). + $rows = get_admin_rumah_ibadah_list($user_id, true); + return $rows[0] ?? null; +} + +function is_admin_rumah_ibadah_id($rumah_ibadah_id, $user_id = null, $only_diterima = true) { + if ($user_id === null) $user_id = (int) $_SESSION['user_id']; + $ids = get_admin_rumah_ibadah_ids($user_id, $only_diterima); + return in_array((int)$rumah_ibadah_id, $ids, true); +} + +function get_nearest_rumah_ibadah($lat, $lng, $max_meter = 300, $admin_user_id = null) { + global $koneksi; + $rows = []; + if ($admin_user_id !== null) { + $stmt = $koneksi->prepare("SELECT * FROM rumah_ibadah WHERE user_id=? AND status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $admin_user_id = (int)$admin_user_id; + $stmt->bind_param('i', $admin_user_id); + $rumahRows = fetch_all_stmt($stmt); + } else { + $rumahRows = []; + $res = $koneksi->query("SELECT * FROM rumah_ibadah WHERE status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + while ($row = $res->fetch_assoc()) $rumahRows[] = $row; + } + + foreach ($rumahRows as $row) { + $jarak = haversine_meter((float) $lat, (float) $lng, (float) $row['latitude'], (float) $row['longitude']); + if ($jarak <= $max_meter) { + $row['jarak_meter'] = round($jarak, 2); + $rows[] = $row; + } + } + usort($rows, fn($a, $b) => $a['jarak_meter'] <=> $b['jarak_meter']); + return $rows[0] ?? null; +} + +function get_penerima_by_id($id) { + global $koneksi; + $stmt = $koneksi->prepare("SELECT pb.*, ri.nama_rumah_ibadah, ri.user_id AS admin_rumah_user_id, ri.latitude AS ri_latitude, ri.longitude AS ri_longitude FROM penerima_bantuan pb LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.id=? LIMIT 1"); + $stmt->bind_param('i', $id); + $stmt->execute(); + return $stmt->get_result()->fetch_assoc(); +} + +function get_rumah_ibadah_by_id($id) { + global $koneksi; + $stmt = $koneksi->prepare("SELECT ri.*, u.nama AS nama_admin, u.username AS username_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.id=? LIMIT 1"); + $stmt->bind_param('i', $id); + $stmt->execute(); + return $stmt->get_result()->fetch_assoc(); +} + +function can_manage_rumah_ibadah($rumah) { + if (!$rumah || !is_logged_in()) return false; + if ($_SESSION['role'] === 'super_admin') return true; + if ($_SESSION['role'] === 'admin_rumah_ibadah') { + return (int)$rumah['user_id'] === (int)$_SESSION['user_id']; + } + return false; +} + +function can_manage_penerima($penerima) { + if (!$penerima || !is_logged_in()) return false; + if ($_SESSION['role'] === 'super_admin') return true; + if ($_SESSION['role'] === 'penerima' && (int) $penerima['user_id'] === (int) $_SESSION['user_id']) return true; + if ($_SESSION['role'] === 'admin_rumah_ibadah') { + return !empty($penerima['rumah_ibadah_id']) && is_admin_rumah_ibadah_id((int)$penerima['rumah_ibadah_id'], null, true); + } + return false; +} + +function insert_riwayat_status($penerima_id, $status_lama, $status_baru, $catatan) { + global $koneksi; + $uid = (int) $_SESSION['user_id']; + $stmt = $koneksi->prepare("INSERT INTO riwayat_status (penerima_id, user_id, status_lama, status_baru, catatan) VALUES (?,?,?,?,?)"); + $stmt->bind_param('iisss', $penerima_id, $uid, $status_lama, $status_baru, $catatan); + $stmt->execute(); +} + +function get_user_by_username_role($username, $roles) { + global $koneksi; + if (!is_array($roles)) $roles = [$roles]; + $username = clean_text($username, 50); + if ($username === '') return null; + $placeholders = implode(',', array_fill(0, count($roles), '?')); + $types = 's' . str_repeat('s', count($roles)); + $params = array_merge([$username], $roles); + $stmt = $koneksi->prepare("SELECT id, nama, username, role, status_akun FROM users WHERE username=? AND role IN ($placeholders) AND status_akun='aktif' LIMIT 1"); + stmt_bind($stmt, $types, $params); + $stmt->execute(); + return $stmt->get_result()->fetch_assoc(); +} + +function has_active_penerima_pin($user_id, $exclude_penerima_id = 0) { + global $koneksi; + $user_id = (int)$user_id; + $exclude_penerima_id = (int)$exclude_penerima_id; + if ($user_id <= 0) return false; + $sql = "SELECT id FROM penerima_bantuan + WHERE user_id=? + AND status_pengajuan IN ('menunggu','diterima') + AND COALESCE(status_hapus,'normal')<>'disetujui'"; + $types = 'i'; + $params = [$user_id]; + if ($exclude_penerima_id > 0) { + $sql .= " AND id<>?"; + $types .= 'i'; + $params[] = $exclude_penerima_id; + } + $sql .= " LIMIT 1"; + $stmt = $koneksi->prepare($sql); + stmt_bind($stmt, $types, $params); + $stmt->execute(); + return (bool)$stmt->get_result()->fetch_assoc(); +} + +function sync_single_penerima_area($penerima_id) { + global $koneksi; + $penerima_id = (int)$penerima_id; + $stmt = $koneksi->prepare("SELECT id, latitude, longitude FROM penerima_bantuan WHERE id=? AND status_pengajuan IN ('menunggu','diterima') AND COALESCE(status_hapus,'normal')<>'disetujui' LIMIT 1"); + $stmt->bind_param('i', $penerima_id); + $stmt->execute(); + $pb = $stmt->get_result()->fetch_assoc(); + if (!$pb) return false; + + $nearest = get_nearest_rumah_ibadah((float)$pb['latitude'], (float)$pb['longitude'], 300); + if ($nearest) { + $riId = (int)$nearest['id']; + $jarak = (float)$nearest['jarak_meter']; + $stmtUp = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=?, jarak_meter=? WHERE id=?"); + $stmtUp->bind_param('idi', $riId, $jarak, $penerima_id); + } else { + $stmtUp = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=NULL, jarak_meter=NULL WHERE id=?"); + $stmtUp->bind_param('i', $penerima_id); + } + return $stmtUp->execute(); +} + +function sync_all_penerima_area() { + global $koneksi; + $ids = []; + $res = $koneksi->query("SELECT id FROM penerima_bantuan WHERE status_pengajuan IN ('menunggu','diterima') AND COALESCE(status_hapus,'normal')<>'disetujui'"); + while ($row = $res->fetch_assoc()) $ids[] = (int)$row['id']; + $updated = 0; + foreach ($ids as $id) { + if (sync_single_penerima_area($id)) $updated++; + } + return $updated; +} + +function sync_penerima_area_by_rumah_ibadah($rumah_ibadah_id) { + // Tetap menerima parameter lama, tetapi sinkronisasi dihitung ulang secara global agar radius beririsan memilih RI terdekat. + return sync_all_penerima_area(); +} + +function detach_penerima_from_rumah_ibadah($rumah_ibadah_id) { + global $koneksi; + $rumah_ibadah_id = (int) $rumah_ibadah_id; + $stmt = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=NULL, jarak_meter=NULL WHERE rumah_ibadah_id=? AND status_pengajuan IN ('menunggu','diterima')"); + $stmt->bind_param('i', $rumah_ibadah_id); + $stmt->execute(); + $affected = $stmt->affected_rows; + // Setelah dilepas, cari ulang rumah ibadah aktif terdekat lain jika ada. + sync_all_penerima_area(); + return $affected; +} + +?> diff --git a/05/dashboard.php b/05/dashboard.php new file mode 100644 index 0000000..ed7e797 --- /dev/null +++ b/05/dashboard.php @@ -0,0 +1,436 @@ +prepare($sql); + if ($types) stmt_bind($stmt, $types, $params); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + return (int) array_values($row)[0]; +} + +$riAdminList = ($role === 'admin_rumah_ibadah') ? get_admin_rumah_ibadah_list((int)$user['id'], true) : []; +$riAdminIds = array_map('intval', array_column($riAdminList, 'id')); +$stats = []; +if ($role === 'super_admin') { + $stats['Total Rumah Ibadah'] = scalar_count("SELECT COUNT(*) FROM rumah_ibadah WHERE COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Rumah Ibadah Menunggu'] = scalar_count("SELECT COUNT(*) FROM rumah_ibadah WHERE status_pengajuan='menunggu' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Total Penerima'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Penerima Menunggu'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE status_pengajuan='menunggu' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Penerima Diterima'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Penerima Ditolak'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE status_pengajuan='ditolak' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Penerima Dibatalkan'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE status_pengajuan='dibatalkan' AND COALESCE(status_hapus,'normal')<>'disetujui'"); + $stats['Program Aktif'] = scalar_count("SELECT COUNT(*) FROM program_bantuan WHERE status_program='aktif'"); + $stats['Bantuan Tersalurkan'] = scalar_count("SELECT COUNT(*) FROM penyaluran_bantuan"); +} elseif ($role === 'admin_rumah_ibadah') { + if (!empty($riAdminIds)) { + $stats['Jumlah Rumah Ibadah'] = count($riAdminIds); + $stats['Rumah Ibadah'] = implode(', ', array_column($riAdminList, 'nama_rumah_ibadah')); + + $types = ''; $params = []; $in = build_in_condition('rumah_ibadah_id', $riAdminIds, $types, $params); + $stats['Pengajuan Dalam Radius'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE $in AND status_pengajuan='menunggu' AND COALESCE(status_hapus,'normal')<>'disetujui'", $types, $params); + + $types = ''; $params = []; $in = build_in_condition('rumah_ibadah_id', $riAdminIds, $types, $params); + $stats['Penerima Diterima'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE $in AND status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'", $types, $params); + + $types = ''; $params = []; $in = build_in_condition('rumah_ibadah_id', $riAdminIds, $types, $params); + $stats['Penerima Ditolak'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE $in AND status_pengajuan='ditolak' AND COALESCE(status_hapus,'normal')<>'disetujui'", $types, $params); + + $types = ''; $params = []; $in = build_in_condition('rumah_ibadah_id', $riAdminIds, $types, $params); + $stats['Program Aktif'] = scalar_count("SELECT COUNT(*) FROM program_bantuan WHERE $in AND status_program='aktif'", $types, $params); + + $types = ''; $params = []; $in = build_in_condition('rumah_ibadah_id', $riAdminIds, $types, $params); + $stats['Bantuan Tersalurkan'] = scalar_count("SELECT COUNT(*) FROM penyaluran_bantuan WHERE $in", $types, $params); + } else { + $stats['Rumah Ibadah'] = 'Belum ada rumah ibadah diterima'; + } +} elseif ($role === 'penerima') { + $uid = (int)$user['id']; + $stats['Pengajuan Saya'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui'", 'i', [$uid]); + $stats['Status Diterima'] = scalar_count("SELECT COUNT(*) FROM penerima_bantuan WHERE user_id=? AND status_pengajuan='diterima' AND COALESCE(status_hapus,'normal')<>'disetujui'", 'i', [$uid]); + $stats['Bantuan Bulan Ini'] = scalar_count("SELECT COUNT(*) FROM penyaluran_bantuan pn JOIN penerima_bantuan pb ON pb.id=pn.penerima_id WHERE pb.user_id=? AND MONTH(pn.tanggal_penyaluran)=? AND YEAR(pn.tanggal_penyaluran)=?", 'iii', [$uid, $bulanIni, $tahunIni]); +} + +$hapusRumahRows = []; +$hapusPenerimaRows = []; +if ($role === 'super_admin') { + $stmt = $koneksi->prepare("SELECT ri.*, u.nama AS nama_admin, u.username AS username_admin FROM rumah_ibadah ri LEFT JOIN users u ON u.id=ri.user_id WHERE ri.status_hapus='menunggu' ORDER BY ri.updated_at DESC"); + $hapusRumahRows = fetch_all_stmt($stmt); + $stmt = $koneksi->prepare("SELECT pb.*, u.username AS username_penerima, ri.nama_rumah_ibadah FROM penerima_bantuan pb LEFT JOIN users u ON u.id=pb.user_id LEFT JOIN rumah_ibadah ri ON ri.id=pb.rumah_ibadah_id WHERE pb.status_hapus='menunggu' ORDER BY pb.updated_at DESC"); + $hapusPenerimaRows = fetch_all_stmt($stmt); +} +?> +Dashboard WebGIS Poverty Mapping + +

Dashboard WebGIS Poverty Mapping

Role: |
Buka Peta Logout
+
+
+
$v): ?>
+ + +
+

Persetujuan Hapus Marker/Pin

+

Admin rumah ibadah dan penerima hanya mengajukan hapus. Marker benar-benar hilang dari peta setelah disetujui super admin.

+

Pengajuan Hapus Rumah Ibadah

+
+ + + + + + + +
Rumah IbadahAdminAlasanAksi
Tidak ada pengajuan hapus rumah ibadah.

|

+ +

Pengajuan Hapus Penerima

+
+ + + + + + + +
PenerimaRumah IbadahAlasanAksi
Tidak ada pengajuan hapus penerima.

User: |
+
+ + + +
+

Kelola Akun Pengguna

+

+ + Super admin dapat mengelola akun penerima dan admin rumah ibadah. Akun super admin lain tidak ditampilkan di daftar ini. + + Admin rumah ibadah hanya dapat mengelola akun penerima. Akun admin rumah ibadah lain tidak bisa dilihat atau diubah. + +

+ + +
+
+
+
+
+
+
+
+
+ +

Daftar Akun

+
+
+
+
+
+
+
AkunRoleStatusRelasi DataDibuat/UpdateAksi
Memuat akun...
+
+ + +
+

Monitoring Penyaluran Per Periode

+
+
+
+
+
+
+
+
+
+
+
+ +
+

Rekap Penyaluran

+
+
+ + +
+
+

Buat Program Bantuan

+
Jenis bantuan, nominal, dan jumlah item yang diatur di sini akan otomatis dipakai saat penyaluran. Petugas tidak perlu mengetik ulang agar data sinkron.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

Daftar Program

+
ProgramPeriodeJenisNominal/ItemStatusAksi
Memuat program...
+
+
+

Catat Penyaluran Bantuan

+
+
+
+
+
+
+
+
+
+ +
+
+ + +
+

Data Laporan Penerima Sudah/Belum Diberi Bantuan

+
PenerimaRumah IbadahProgramTargetJumlahStatusTerakhirRiwayat Bulan IniAksi
Memuat data...
+
+ +
+

Riwayat Pengajuan dan Bantuan

+
Memuat riwayat...
+
+
+ diff --git a/05/edit_penerima.php b/05/edit_penerima.php new file mode 100644 index 0000000..ea4d542 --- /dev/null +++ b/05/edit_penerima.php @@ -0,0 +1,70 @@ + 0.000001 || abs((float)$lng - $lngBefore) > 0.000001); + +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + $globalNearest = get_nearest_rumah_ibadah((float)$lat, (float)$lng, 300); + $ownNearest = get_nearest_rumah_ibadah((float)$lat, (float)$lng, 300, (int)$_SESSION['user_id']); + if (!$ownNearest) json_response('err', 'Lokasi baru di luar radius 300 meter seluruh rumah ibadah Anda.'); + if ($globalNearest && (int)$globalNearest['id'] !== (int)$ownNearest['id']) { + json_response('err', 'Lokasi baru lebih dekat ke rumah ibadah lain. Sistem hanya boleh mengaitkan penerima ke rumah ibadah terdekat.', [], 409); + } + $rumah_ibadah_id = (int)$ownNearest['id']; + $jarak_meter = (float)$ownNearest['jarak_meter']; +} else { + $nearest = get_nearest_rumah_ibadah((float)$lat, (float)$lng, 300); + if ($nearest) { + $rumah_ibadah_id = (int)$nearest['id']; + $jarak_meter = (float)$nearest['jarak_meter']; + } +} + +// FIX: pisahkan cabang UPDATE agar rumah_ibadah_id/jarak_meter NULL tersimpan benar, bukan 0. +if ($rumah_ibadah_id !== null) { + $stmt = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=?, nama_penerima=?, nik=?, no_hp=?, alamat=?, pekerjaan=?, penghasilan=?, jumlah_tanggungan=?, kondisi_rumah=?, jenis_bantuan=?, latitude=?, longitude=?, jarak_meter=? WHERE id=?"); + $stmt->bind_param('isssssiissdddi', $rumah_ibadah_id, $nama, $nik, $no_hp, $alamat, $pekerjaan, $penghasilan, $jumlah_tanggungan, $kondisi_rumah, $jenis_bantuan, $lat, $lng, $jarak_meter, $id); +} else { + $stmt = $koneksi->prepare("UPDATE penerima_bantuan SET rumah_ibadah_id=NULL, nama_penerima=?, nik=?, no_hp=?, alamat=?, pekerjaan=?, penghasilan=?, jumlah_tanggungan=?, kondisi_rumah=?, jenis_bantuan=?, latitude=?, longitude=?, jarak_meter=NULL WHERE id=?"); + $stmt->bind_param('sssssiissddi', $nama, $nik, $no_hp, $alamat, $pekerjaan, $penghasilan, $jumlah_tanggungan, $kondisi_rumah, $jenis_bantuan, $lat, $lng, $id); +} +if (!$stmt->execute()) json_response('err', 'Gagal mengubah data: ' . $koneksi->error); + +// FIX: catat riwayat saat lokasi berubah agar ada jejak audit. +if ($lokasiBerubar) { + $riNama = $rumah_ibadah_id ? ('RI ID ' . $rumah_ibadah_id) : 'belum terhubung RI'; + insert_riwayat_status($id, $penerima['status_pengajuan'], $penerima['status_pengajuan'], "Lokasi penerima diperbarui. RI baru: $riNama, jarak: " . ($jarak_meter ?? 'N/A') . " m."); +} + +json_response('ok', 'Data penerima berhasil diubah.', get_penerima_by_id($id)); +?> diff --git a/05/edit_program_bantuan.php b/05/edit_program_bantuan.php new file mode 100644 index 0000000..7a61668 --- /dev/null +++ b/05/edit_program_bantuan.php @@ -0,0 +1,36 @@ +prepare("SELECT * FROM program_bantuan WHERE id=? LIMIT 1"); +$stmt->bind_param('i', $id); +$program = fetch_all_stmt($stmt)[0] ?? null; +if (!$program) json_response('err', 'Program tidak ditemukan.'); +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + if (!is_admin_rumah_ibadah_id((int)$program['rumah_ibadah_id'], (int)$_SESSION['user_id'], true)) { + json_response('err', 'Akses ditolak. Program ini bukan milik rumah ibadah yang dikelola akun Anda.', [], 403); + } +} + +$jenis = post('jenis_bantuan'); +$status = post('status_program', 'aktif'); +if (!valid_enum($jenis, ['zakat','sembako','uang_tunai','lainnya']) || !valid_enum($status, ['aktif','selesai','dibatalkan'])) json_response('err', 'Pilihan tidak valid.'); +$nama = clean_text(post('nama_program'), 150); +$deskripsi = clean_text(post('deskripsi'), 1500); +$bulan = (int) post('bulan'); +$tahun = (int) post('tahun'); +$target = (int) post('target_frekuensi', 1); +$nominalInput = post('nominal', ''); +$nominal = $nominalInput === '' ? null : (int) $nominalInput; +$jumlah_item = clean_text(post('jumlah_item', ''), 100); +if ($nama==='' || $bulan<1 || $bulan>12 || $tahun<2000 || $target<1) json_response('err', 'Data program tidak valid.'); +if ($nominal !== null && $nominal < 0) json_response('err', 'Nominal tidak boleh minus.'); + +$stmt = $koneksi->prepare("UPDATE program_bantuan SET nama_program=?, jenis_bantuan=?, deskripsi=?, bulan=?, tahun=?, target_frekuensi=?, nominal=?, jumlah_item=?, status_program=? WHERE id=?"); +$stmt->bind_param('sssiiiissi', $nama, $jenis, $deskripsi, $bulan, $tahun, $target, $nominal, $jumlah_item, $status, $id); +if (!$stmt->execute()) json_response('err', 'Gagal mengubah program: ' . $koneksi->error); +json_response('ok', 'Program bantuan berhasil diubah.', ['id' => $id]); +?> diff --git a/05/edit_user.php b/05/edit_user.php new file mode 100644 index 0000000..dad8c20 --- /dev/null +++ b/05/edit_user.php @@ -0,0 +1,86 @@ +prepare("SELECT id, role FROM users WHERE id=? LIMIT 1"); +$stmt->bind_param('i', $id); +$stmt->execute(); +$target = $stmt->get_result()->fetch_assoc(); +if (!$target) json_response('err', 'Akun tidak ditemukan.', [], 404); + +if ($actorRole === 'super_admin') { + if (!in_array($role, ['admin_rumah_ibadah', 'penerima'], true)) { + json_response('err', 'Super admin hanya mengelola akun admin rumah ibadah dan penerima dari halaman ini.', [], 403); + } + if (!in_array($target['role'], ['admin_rumah_ibadah', 'penerima'], true)) { + json_response('err', 'Akun ini tidak boleh diubah dari halaman ini.', [], 403); + } +} else { + // Admin rumah ibadah hanya bisa mengubah akun penerima, dan role tetap penerima. + if ($target['role'] !== 'penerima') json_response('err', 'Admin rumah ibadah hanya boleh mengelola akun penerima.', [], 403); + $role = 'penerima'; + + $ids = get_admin_rumah_ibadah_ids((int)$actor['id'], true); + $types = 'i'; + $params = [$id]; + if (!empty($ids)) { + $notIn = build_in_condition('rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penerima_bantuan WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui' AND rumah_ibadah_id IS NOT NULL AND NOT ($notIn)"); + } else { + $stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penerima_bantuan WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui' AND rumah_ibadah_id IS NOT NULL"); + } + stmt_bind($stmt, $types, $params); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + if ((int)$row['jml'] > 0) json_response('err', 'Akun penerima ini terhubung dengan rumah ibadah lain.', [], 403); +} + +$stmt = $koneksi->prepare("SELECT id FROM users WHERE username=? AND id<>? LIMIT 1"); +$stmt->bind_param('si', $username, $id); +$stmt->execute(); +if ($stmt->get_result()->fetch_assoc()) json_response('err', 'Username sudah digunakan akun lain.', [], 409); + +if ($password !== '') { + $hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $koneksi->prepare("UPDATE users SET nama=?, username=?, password=?, role=?, status_akun=? WHERE id=?"); + $stmt->bind_param('sssssi', $nama, $username, $hash, $role, $status_akun, $id); +} else { + $stmt = $koneksi->prepare("UPDATE users SET nama=?, username=?, role=?, status_akun=? WHERE id=?"); + $stmt->bind_param('ssssi', $nama, $username, $role, $status_akun, $id); +} +if (!$stmt->execute()) json_response('err', 'Gagal mengubah akun: ' . $stmt->error, [], 500); + +json_response('ok', 'Akun berhasil diubah.', [ + 'id' => $id, + 'nama' => $nama, + 'username' => $username, + 'role' => $role, + 'status_akun' => $status_akun +]); +?> diff --git a/05/hapus_penerima.php b/05/hapus_penerima.php new file mode 100644 index 0000000..3dcde8a --- /dev/null +++ b/05/hapus_penerima.php @@ -0,0 +1,35 @@ +prepare("UPDATE penerima_bantuan SET status_pengajuan=?, status_hapus=?, alasan_hapus=?, alasan_status=? WHERE id=?"); + $stmt->bind_param('ssssi', $statusBaru, $hapus, $catatan, $catatan, $id); + if (!$stmt->execute()) json_response('err', 'Gagal menghapus marker penerima: ' . $koneksi->error); + insert_riwayat_status($id, $statusLama, 'hapus_disetujui', $catatan); + json_response('ok', 'Marker penerima berhasil dihapus oleh super admin.', get_penerima_by_id($id)); +} + +// Admin rumah ibadah dan penerima hanya mengajukan penghapusan, menunggu persetujuan super admin. +if (!can_manage_penerima($penerima)) json_response('err', 'Akses ditolak.', [], 403); +if ($alasan === '') json_response('err', 'Alasan penghapusan wajib diisi.'); + +$statusHapus = 'menunggu'; +$stmt = $koneksi->prepare("UPDATE penerima_bantuan SET status_hapus=?, alasan_hapus=? WHERE id=?"); +$stmt->bind_param('ssi', $statusHapus, $alasan, $id); +if (!$stmt->execute()) json_response('err', 'Gagal mengajukan penghapusan marker penerima: ' . $koneksi->error); +insert_riwayat_status($id, $penerima['status_pengajuan'], 'hapus_menunggu', $alasan); +json_response('ok', 'Pengajuan hapus marker penerima dikirim. Menunggu persetujuan super admin.', get_penerima_by_id($id)); +?> diff --git a/05/hapus_program_bantuan.php b/05/hapus_program_bantuan.php new file mode 100644 index 0000000..c2c4668 --- /dev/null +++ b/05/hapus_program_bantuan.php @@ -0,0 +1,32 @@ +prepare("SELECT * FROM program_bantuan WHERE id=? LIMIT 1"); +$stmt->bind_param('i', $id); +$program = fetch_all_stmt($stmt)[0] ?? null; +if (!$program) json_response('err', 'Program tidak ditemukan.'); +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + if (!is_admin_rumah_ibadah_id((int)$program['rumah_ibadah_id'], (int)$_SESSION['user_id'], true)) { + json_response('err', 'Akses ditolak. Program ini bukan milik rumah ibadah yang dikelola akun Anda.', [], 403); + } +} + +$stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penyaluran_bantuan WHERE program_id=?"); +$stmt->bind_param('i', $id); +$row = fetch_all_stmt($stmt)[0]; +if ((int)$row['jml'] > 0) { + $status = 'dibatalkan'; + $stmt = $koneksi->prepare("UPDATE program_bantuan SET status_program=? WHERE id=?"); + $stmt->bind_param('si', $status, $id); + $stmt->execute(); + json_response('ok', 'Program sudah memiliki penyaluran, maka status diubah menjadi dibatalkan.', ['id'=>$id]); +} +$stmt = $koneksi->prepare("DELETE FROM program_bantuan WHERE id=?"); +$stmt->bind_param('i', $id); +if (!$stmt->execute()) json_response('err', 'Gagal menghapus program: ' . $koneksi->error); +json_response('ok', 'Program bantuan berhasil dihapus.', ['id'=>$id]); +?> diff --git a/05/hapus_rumah_ibadah.php b/05/hapus_rumah_ibadah.php new file mode 100644 index 0000000..cc731fd --- /dev/null +++ b/05/hapus_rumah_ibadah.php @@ -0,0 +1,36 @@ +prepare("UPDATE rumah_ibadah SET status_pengajuan=?, status_hapus=?, alasan_hapus=?, keterangan=? WHERE id=?"); + $stmt->bind_param('ssssi', $status, $hapus, $catatan, $catatan, $id); + if (!$stmt->execute()) json_response('err', 'Gagal menghapus marker rumah ibadah: ' . $koneksi->error); + $jumlahLepas = detach_penerima_from_rumah_ibadah($id); + $row = get_rumah_ibadah_by_id($id); + $row['jumlah_penerima_dilepas'] = $jumlahLepas; + json_response('ok', 'Marker rumah ibadah berhasil dihapus oleh super admin.', $row); +} + +// Admin rumah ibadah hanya boleh mengajukan hapus untuk rumah ibadah miliknya. +if ((int)$rumah['user_id'] !== (int)$_SESSION['user_id']) json_response('err', 'Akses ditolak. Admin hanya boleh mengajukan hapus rumah ibadah miliknya.', [], 403); +if ($alasan === '') json_response('err', 'Alasan penghapusan wajib diisi.'); + +$statusHapus = 'menunggu'; +$stmt = $koneksi->prepare("UPDATE rumah_ibadah SET status_hapus=?, alasan_hapus=? WHERE id=?"); +$stmt->bind_param('ssi', $statusHapus, $alasan, $id); +if (!$stmt->execute()) json_response('err', 'Gagal mengajukan penghapusan rumah ibadah: ' . $koneksi->error); +json_response('ok', 'Pengajuan hapus marker rumah ibadah dikirim. Menunggu persetujuan super admin.', get_rumah_ibadah_by_id($id)); +?> diff --git a/05/hapus_user.php b/05/hapus_user.php new file mode 100644 index 0000000..0ab5385 --- /dev/null +++ b/05/hapus_user.php @@ -0,0 +1,71 @@ +prepare("SELECT id, role FROM users WHERE id=? LIMIT 1"); +$stmt->bind_param('i', $id); +$stmt->execute(); +$target = $stmt->get_result()->fetch_assoc(); +if (!$target) json_response('err', 'Akun tidak ditemukan.', [], 404); + +if ($actorRole === 'super_admin') { + if (!in_array($target['role'], ['admin_rumah_ibadah', 'penerima'], true)) { + json_response('err', 'Super admin hanya menghapus/menonaktifkan akun admin rumah ibadah dan penerima dari halaman ini.', [], 403); + } +} else { + if ($target['role'] !== 'penerima') json_response('err', 'Admin rumah ibadah hanya boleh menghapus/menonaktifkan akun penerima.', [], 403); + $ids = get_admin_rumah_ibadah_ids((int)$actor['id'], true); + $types = 'i'; + $params = [$id]; + if (!empty($ids)) { + $notIn = build_in_condition('rumah_ibadah_id', $ids, $types, $params); + $stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penerima_bantuan WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui' AND rumah_ibadah_id IS NOT NULL AND NOT ($notIn)"); + } else { + $stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penerima_bantuan WHERE user_id=? AND COALESCE(status_hapus,'normal')<>'disetujui' AND rumah_ibadah_id IS NOT NULL"); + } + stmt_bind($stmt, $types, $params); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + if ((int)$row['jml'] > 0) json_response('err', 'Akun penerima ini terhubung dengan rumah ibadah lain.', [], 403); +} + +// Cek relasi agar tidak merusak foreign key dan riwayat audit. +$related = 0; +$checks = [ + ["SELECT COUNT(*) AS jml FROM rumah_ibadah WHERE user_id=?", 'i'], + ["SELECT COUNT(*) AS jml FROM penerima_bantuan WHERE user_id=?", 'i'], + ["SELECT COUNT(*) AS jml FROM penyaluran_bantuan WHERE admin_id=?", 'i'], + ["SELECT COUNT(*) AS jml FROM riwayat_status WHERE user_id=?", 'i'] +]; +foreach ($checks as $c) { + $stmt = $koneksi->prepare($c[0]); + $stmt->bind_param('i', $id); + $stmt->execute(); + $row = $stmt->get_result()->fetch_assoc(); + $related += (int)$row['jml']; +} + +if ($related > 0) { + $stmt = $koneksi->prepare("UPDATE users SET status_akun='nonaktif' WHERE id=?"); + $stmt->bind_param('i', $id); + if (!$stmt->execute()) json_response('err', 'Gagal menonaktifkan akun: ' . $stmt->error, [], 500); + json_response('ok', 'Akun memiliki relasi data, jadi tidak dihapus permanen. Akun sudah dinonaktifkan.', ['mode' => 'nonaktif']); +} + +$stmt = $koneksi->prepare("DELETE FROM users WHERE id=?"); +$stmt->bind_param('i', $id); +if (!$stmt->execute()) json_response('err', 'Gagal menghapus akun: ' . $stmt->error, [], 500); +json_response('ok', 'Akun berhasil dihapus permanen.', ['mode' => 'delete']); +?> diff --git a/05/index.php b/05/index.php new file mode 100644 index 0000000..c350815 --- /dev/null +++ b/05/index.php @@ -0,0 +1,286 @@ + + + + + + + WebGIS Poverty Mapping + + + + + +
+ + + + diff --git a/05/koneksi.php b/05/koneksi.php new file mode 100644 index 0000000..890d992 --- /dev/null +++ b/05/koneksi.php @@ -0,0 +1,23 @@ +connect_error) { + http_response_code(500); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode([ + 'status' => 'err', + 'message' => 'Koneksi database gagal: ' . $koneksi->connect_error + ]); + exit; +} + +$koneksi->set_charset('utf8mb4'); +?> diff --git a/05/login.php b/05/login.php new file mode 100644 index 0000000..2605abe --- /dev/null +++ b/05/login.php @@ -0,0 +1,53 @@ +prepare("SELECT * FROM users WHERE username=? AND status_akun='aktif' LIMIT 1"); + $stmt->bind_param('s', $username); + $stmt->execute(); + $user = $stmt->get_result()->fetch_assoc(); + + // password_verify() digunakan agar password tidak disimpan plain text. + if ($user && password_verify($password, $user['password'])) { + $_SESSION['user_id'] = (int) $user['id']; + $_SESSION['nama'] = $user['nama']; + $_SESSION['role'] = $user['role']; + header('Location: dashboard.php'); + exit; + } + $error = 'Username atau password salah.'; +} +?> + + + + + +Login - WebGIS Poverty Mapping + + + +
+

WebGIS Poverty Mapping

Manajemen bantuan/zakat berbasis lokasi
+
+
+
+
+ +
+ +
Akun awal:
username superadmin
password password

Catatan: registrasi hanya untuk penerima. Akun admin rumah ibadah dibuat oleh super admin dari dashboard.
+
+ + diff --git a/05/logout.php b/05/logout.php new file mode 100644 index 0000000..bb3dc74 --- /dev/null +++ b/05/logout.php @@ -0,0 +1,7 @@ + diff --git a/05/register.php b/05/register.php new file mode 100644 index 0000000..661a3a5 --- /dev/null +++ b/05/register.php @@ -0,0 +1,58 @@ +prepare("SELECT id FROM users WHERE username=? LIMIT 1"); + $cek->bind_param('s', $username); + $cek->execute(); + if ($cek->get_result()->fetch_assoc()) { + $error = 'Username sudah digunakan.'; + } else { + // password_hash() untuk keamanan password. + $hash = password_hash($password, PASSWORD_DEFAULT); + $role = 'penerima'; + $stmt = $koneksi->prepare("INSERT INTO users (nama, username, password, role) VALUES (?,?,?,?)"); + $stmt->bind_param('ssss', $nama, $username, $hash, $role); + if ($stmt->execute()) { + $success = 'Registrasi berhasil. Silakan login.'; + } else { + $error = 'Registrasi gagal: ' . $koneksi->error; + } + } + } +} +?> + + + + +Registrasi Penerima + + +
+

Registrasi Penerima

Akun ini digunakan untuk mengajukan penerima bantuan melalui peta.
+
+
+
+
+
+
+
+ +
+ +
diff --git a/05/tambah_penerima.php b/05/tambah_penerima.php new file mode 100644 index 0000000..7e380d0 --- /dev/null +++ b/05/tambah_penerima.php @@ -0,0 +1,83 @@ +prepare("INSERT INTO penerima_bantuan (user_id, rumah_ibadah_id, nama_penerima, nik, no_hp, alamat, pekerjaan, penghasilan, jumlah_tanggungan, kondisi_rumah, jenis_bantuan, latitude, longitude, jarak_meter, status_pengajuan, sumber_pengajuan) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + $stmt->bind_param('iisssssiissdddss', $user_id, $rumah_ibadah_id, $nama, $nik, $no_hp, $alamat, $pekerjaan, $penghasilan, $jumlah_tanggungan, $kondisi_rumah, $jenis_bantuan, $lat, $lng, $jarak_meter, $status, $sumber); +} else { + $stmt = $koneksi->prepare("INSERT INTO penerima_bantuan (user_id, rumah_ibadah_id, nama_penerima, nik, no_hp, alamat, pekerjaan, penghasilan, jumlah_tanggungan, kondisi_rumah, jenis_bantuan, latitude, longitude, jarak_meter, status_pengajuan, sumber_pengajuan) VALUES (?,NULL,?,?,?,?,?,?,?,?,?,?,?,NULL,?,?)"); + $stmt->bind_param('isssssiissddss', $user_id, $nama, $nik, $no_hp, $alamat, $pekerjaan, $penghasilan, $jumlah_tanggungan, $kondisi_rumah, $jenis_bantuan, $lat, $lng, $status, $sumber); +} +if (!$stmt->execute()) json_response('err', 'Gagal menyimpan penerima: ' . $koneksi->error); + +$id = $stmt->insert_id; +insert_riwayat_status($id, null, $status, 'Pengajuan penerima dibuat.'); +$row = get_penerima_by_id($id); +$msg = $rumah_ibadah_id ? 'Pengajuan penerima berhasil disimpan dan masuk radius rumah ibadah terdekat.' : 'Pengajuan tersimpan, tetapi belum ada pemberi bantuan dalam radius 300 meter. Nanti akan otomatis terhubung jika ada rumah ibadah diterima di radius tersebut.'; +json_response('ok', $msg, $row); +?> diff --git a/05/tambah_penyaluran.php b/05/tambah_penyaluran.php new file mode 100644 index 0000000..ae20572 --- /dev/null +++ b/05/tambah_penyaluran.php @@ -0,0 +1,76 @@ +prepare("SELECT p.*, ri.user_id AS admin_rumah_user_id, ri.latitude AS ri_latitude, ri.longitude AS ri_longitude, ri.nama_rumah_ibadah FROM program_bantuan p LEFT JOIN rumah_ibadah ri ON ri.id=p.rumah_ibadah_id WHERE p.id=? AND COALESCE(ri.status_hapus,'normal')<>'disetujui' LIMIT 1"); +$stmt->bind_param('i', $program_id); +$program = fetch_all_stmt($stmt)[0] ?? null; +if (!$program || $program['status_program'] === 'dibatalkan') json_response('err', 'Program bantuan tidak ditemukan atau dibatalkan.'); + +// FIX: cek status program selesai — tidak bisa menyalurkan ke program yang sudah selesai +if ($program['status_program'] === 'selesai') json_response('err', 'Program bantuan sudah berstatus selesai, tidak dapat menyalurkan bantuan baru.', [], 409); + +$jenis = $program['jenis_bantuan']; +$nominal = $program['nominal'] === null ? null : (int)$program['nominal']; +$jumlah_item = $program['jumlah_item'] === null ? '' : (string)$program['jumlah_item']; + +$penerima = get_penerima_by_id($penerima_id); +if (!$penerima || $penerima['status_pengajuan'] !== 'diterima' || ($penerima['status_hapus'] ?? 'normal') === 'disetujui') json_response('err', 'Penerima tidak ditemukan, belum diterima, atau marker sudah dihapus.'); + +$rumah_ibadah_id = (int) $program['rumah_ibadah_id']; +if (!$rumah_ibadah_id) json_response('err', 'Program belum terhubung ke rumah ibadah.'); +if ((int)$penerima['rumah_ibadah_id'] !== $rumah_ibadah_id) { + json_response('err', 'Program dan penerima tidak berada pada rumah ibadah yang sama. Sinkronkan data penerima terlebih dahulu.', [], 409); +} + +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + if (!is_admin_rumah_ibadah_id($rumah_ibadah_id, (int)$_SESSION['user_id'], true)) { + json_response('err', 'Anda hanya bisa menyalurkan bantuan untuk seluruh rumah ibadah milik akun Anda sendiri.', [], 403); + } +} + +$jarak = haversine_meter((float)$penerima['latitude'], (float)$penerima['longitude'], (float)$program['ri_latitude'], (float)$program['ri_longitude']); +if ($jarak > 300) json_response('err', 'Penerima berada di luar radius 300 meter rumah ibadah program.'); + +$bulan = (int) date('m', strtotime($tanggal)); +$tahun = (int) date('Y', strtotime($tanggal)); +$stmt = $koneksi->prepare("SELECT COUNT(*) AS jml FROM penyaluran_bantuan WHERE penerima_id=? AND program_id=? AND MONTH(tanggal_penyaluran)=? AND YEAR(tanggal_penyaluran)=?"); +$stmt->bind_param('iiii', $penerima_id, $program_id, $bulan, $tahun); +$count = fetch_all_stmt($stmt)[0]; +$bantuan_ke = ((int)$count['jml']) + 1; +$target = (int)$program['target_frekuensi']; +$warning = ''; +if ($bantuan_ke > $target) { + $warning = "Peringatan: penerima ini sudah/akan menerima bantuan $bantuan_ke kali pada program ini. Target program adalah $target kali. Data tetap dicatat untuk audit."; +} + +$admin_id = (int) $_SESSION['user_id']; + +// FIX: pisahkan cabang INSERT agar nominal NULL tersimpan sebagai NULL, bukan 0. +if ($nominal !== null) { + $stmt = $koneksi->prepare("INSERT INTO penyaluran_bantuan (program_id, penerima_id, rumah_ibadah_id, admin_id, jenis_bantuan, nominal, jumlah_item, bantuan_ke, keterangan, tanggal_penyaluran) VALUES (?,?,?,?,?,?,?,?,?,?)"); + $stmt->bind_param('iiiisisiss', $program_id, $penerima_id, $rumah_ibadah_id, $admin_id, $jenis, $nominal, $jumlah_item, $bantuan_ke, $ket, $tanggal); +} else { + $stmt = $koneksi->prepare("INSERT INTO penyaluran_bantuan (program_id, penerima_id, rumah_ibadah_id, admin_id, jenis_bantuan, nominal, jumlah_item, bantuan_ke, keterangan, tanggal_penyaluran) VALUES (?,?,?,?,?,NULL,?,?,?,?)"); + $stmt->bind_param('iiiississ', $program_id, $penerima_id, $rumah_ibadah_id, $admin_id, $jenis, $jumlah_item, $bantuan_ke, $ket, $tanggal); +} +if (!$stmt->execute()) json_response('err', 'Gagal mencatat penyaluran bantuan: ' . $koneksi->error); + +json_response('ok', 'Penyaluran bantuan berhasil dicatat sesuai master program.' . ($warning ? ' ' . $warning : ''), [ + 'id' => $stmt->insert_id, + 'bantuan_ke' => $bantuan_ke, + 'jenis_bantuan' => $jenis, + 'nominal' => $nominal, + 'jumlah_item' => $jumlah_item, + 'warning' => $warning +]); +?> diff --git a/05/tambah_program_bantuan.php b/05/tambah_program_bantuan.php new file mode 100644 index 0000000..0061841 --- /dev/null +++ b/05/tambah_program_bantuan.php @@ -0,0 +1,48 @@ + 12 || $tahun < 2000 || $target < 1) json_response('err', 'Bulan, tahun, atau target frekuensi tidak valid.'); +if ($nominal !== null && $nominal < 0) json_response('err', 'Nominal tidak boleh minus.'); + +if ($_SESSION['role'] === 'admin_rumah_ibadah') { + $ids = get_admin_rumah_ibadah_ids((int)$_SESSION['user_id'], true); + if (empty($ids)) json_response('err', 'Admin belum memiliki rumah ibadah diterima.'); + if ($rumah_ibadah_id === '') { + if (count($ids) > 1) json_response('err', 'Pilih rumah ibadah terlebih dahulu karena akun ini mengelola lebih dari satu rumah ibadah.', [], 422); + $rumah_ibadah_id = $ids[0]; + } else { + $rumah_ibadah_id = (int)$rumah_ibadah_id; + if (!in_array($rumah_ibadah_id, $ids, true)) json_response('err', 'Rumah ibadah tidak termasuk milik admin ini.', [], 403); + } +} else { + $rumah_ibadah_id = ($rumah_ibadah_id === '') ? null : (int) $rumah_ibadah_id; + if (!$rumah_ibadah_id) json_response('err', 'Rumah ibadah wajib dipilih.'); +} + +// FIX: pisahkan cabang INSERT agar nominal NULL tersimpan sebagai NULL, bukan 0. +if ($nominal !== null) { + $stmt = $koneksi->prepare("INSERT INTO program_bantuan (rumah_ibadah_id, nama_program, jenis_bantuan, deskripsi, bulan, tahun, target_frekuensi, nominal, jumlah_item) VALUES (?,?,?,?,?,?,?,?,?)"); + $stmt->bind_param('isssiiiis', $rumah_ibadah_id, $nama, $jenis, $deskripsi, $bulan, $tahun, $target, $nominal, $jumlah_item); +} else { + $stmt = $koneksi->prepare("INSERT INTO program_bantuan (rumah_ibadah_id, nama_program, jenis_bantuan, deskripsi, bulan, tahun, target_frekuensi, nominal, jumlah_item) VALUES (?,?,?,?,?,?,?,NULL,?)"); + $stmt->bind_param('isssiiis', $rumah_ibadah_id, $nama, $jenis, $deskripsi, $bulan, $tahun, $target, $jumlah_item); +} +if (!$stmt->execute()) json_response('err', 'Gagal membuat program bantuan: ' . $koneksi->error); +json_response('ok', 'Program bantuan berhasil dibuat. Jenis, nominal, dan jumlah item akan menjadi acuan mutlak saat penyaluran.', ['id' => $stmt->insert_id]); +?> diff --git a/05/tambah_user.php b/05/tambah_user.php new file mode 100644 index 0000000..76faf72 --- /dev/null +++ b/05/tambah_user.php @@ -0,0 +1,63 @@ +prepare("SELECT id FROM users WHERE username=? LIMIT 1"); +$stmt->bind_param('s', $username); +$stmt->execute(); +if ($stmt->get_result()->fetch_assoc()) { + json_response('err', 'Username sudah digunakan.', [], 409); +} + +$hash = password_hash($password, PASSWORD_DEFAULT); +$stmt = $koneksi->prepare("INSERT INTO users (nama, username, password, role, status_akun) VALUES (?,?,?,?,?)"); +$stmt->bind_param('sssss', $nama, $username, $hash, $role, $status_akun); +if (!$stmt->execute()) { + json_response('err', 'Gagal membuat akun: ' . $stmt->error, [], 500); +} + +json_response('ok', 'Akun berhasil dibuat.', [ + 'id' => $stmt->insert_id, + 'nama' => $nama, + 'username' => $username, + 'role' => $role, + 'status_akun' => $status_akun +]); +?> diff --git a/05/update_status_penerima.php b/05/update_status_penerima.php new file mode 100644 index 0000000..21b2cf4 --- /dev/null +++ b/05/update_status_penerima.php @@ -0,0 +1,56 @@ +prepare("UPDATE penerima_bantuan SET status_pengajuan=?, status_hapus=?, alasan_hapus=?, alasan_status=? WHERE id=?"); + $stmt->bind_param('ssssi', $statusPengajuan, $statusHapus, $catatan, $catatan, $id); + } else { + $statusHapus = 'ditolak'; + $stmt = $koneksi->prepare("UPDATE penerima_bantuan SET status_hapus=?, alasan_hapus=? WHERE id=?"); + $stmt->bind_param('ssi', $statusHapus, $catatan, $id); + } + if (!$stmt->execute()) json_response('err', 'Gagal memproses persetujuan hapus penerima: ' . $koneksi->error); + insert_riwayat_status($id, $penerima['status_pengajuan'], $status, $catatan); + json_response('ok', 'Persetujuan hapus marker penerima berhasil diproses.', get_penerima_by_id($id)); +} + +if (!can_manage_penerima($penerima)) json_response('err', 'Data tidak ditemukan atau akses ditolak.', [], 403); + +// FIX: admin_rumah_ibadah tidak boleh memundurkan status ke 'menunggu' jika sudah 'diterima' atau 'ditolak'. +// Hanya super_admin yang bisa mereset status. +if ($status === 'menunggu' && $_SESSION['role'] === 'admin_rumah_ibadah') { + json_response('err', 'Admin rumah ibadah tidak dapat mereset status penerima ke menunggu. Hubungi super admin.', [], 403); +} + +// FIX: admin_rumah_ibadah tidak bisa membatalkan penerima yang sudah 'diterima' — hanya super_admin. +if ($status === 'dibatalkan' && $penerima['status_pengajuan'] === 'diterima' && $_SESSION['role'] === 'admin_rumah_ibadah') { + json_response('err', 'Admin rumah ibadah tidak dapat membatalkan penerima yang sudah berstatus diterima. Hubungi super admin.', [], 403); +} + +$status_lama = $penerima['status_pengajuan']; +$stmt = $koneksi->prepare("UPDATE penerima_bantuan SET status_pengajuan=?, alasan_status=? WHERE id=?"); +$stmt->bind_param('ssi', $status, $alasan, $id); +if (!$stmt->execute()) json_response('err', 'Gagal update status: ' . $koneksi->error); + +insert_riwayat_status($id, $status_lama, $status, $alasan ?: 'Status penerima diubah.'); +$row = get_penerima_by_id($id); +json_response('ok', 'Status penerima berhasil diubah.', $row); +?> diff --git a/05/update_status_rumah_ibadah.php b/05/update_status_rumah_ibadah.php new file mode 100644 index 0000000..df391a9 --- /dev/null +++ b/05/update_status_rumah_ibadah.php @@ -0,0 +1,63 @@ +prepare("UPDATE rumah_ibadah SET status_pengajuan=?, status_hapus=?, alasan_hapus=?, keterangan=? WHERE id=?"); + $stmt->bind_param('ssssi', $statusPengajuan, $statusHapus, $catatan, $catatan, $id); + if (!$stmt->execute()) json_response('err', 'Gagal menyetujui hapus rumah ibadah: ' . $koneksi->error); + $jumlahLepas = detach_penerima_from_rumah_ibadah($id); + } else { + $statusHapus = 'ditolak'; + $stmt = $koneksi->prepare("UPDATE rumah_ibadah SET status_hapus=?, alasan_hapus=? WHERE id=?"); + $stmt->bind_param('ssi', $statusHapus, $catatan, $id); + if (!$stmt->execute()) json_response('err', 'Gagal menolak hapus rumah ibadah: ' . $koneksi->error); + } + + $row = get_rumah_ibadah_by_id($id); + $row['jumlah_penerima_tersinkron'] = 0; + $row['jumlah_penerima_dilepas'] = $jumlahLepas; + json_response('ok', 'Persetujuan hapus marker rumah ibadah berhasil diproses.', $row); +} + +// Update status pengajuan rumah ibadah biasa. +$statusHapus = 'normal'; +$stmt = $koneksi->prepare("UPDATE rumah_ibadah SET status_pengajuan=?, status_hapus=?, keterangan=? WHERE id=?"); +$stmt->bind_param('sssi', $status, $statusHapus, $ket, $id); +if (!$stmt->execute()) json_response('err', 'Gagal update status rumah ibadah: ' . $koneksi->error); + +if ($status === 'diterima') { + // Saat rumah ibadah disetujui, penerima yang dibuat lebih dulu otomatis masuk area jika radius <= 300 meter. + $jumlahSinkron = sync_penerima_area_by_rumah_ibadah($id); +} elseif ($status === 'ditolak') { + // Jika rumah ibadah ditolak, penerima yang sebelumnya terkait dilepas agar tidak mengarah ke pemberi bantuan tidak aktif. + $jumlahLepas = detach_penerima_from_rumah_ibadah($id); +} + +$row = get_rumah_ibadah_by_id($id); +if ($row) { + $row['jumlah_penerima_tersinkron'] = $jumlahSinkron; + $row['jumlah_penerima_dilepas'] = $jumlahLepas; +} +$msg = 'Status rumah ibadah berhasil diubah.'; +if ($jumlahSinkron) $msg .= " $jumlahSinkron penerima otomatis masuk area tanggung jawab."; +if ($jumlahLepas) $msg .= " $jumlahLepas penerima dilepas dari rumah ibadah ini."; +json_response('ok', $msg, $row); +?> diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e2fe305 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM php:8.2-apache + +RUN docker-php-ext-install mysqli pdo pdo_mysql && a2enmod rewrite && echo "ServerName localhost" >> /etc/apache2/apache2.conf + +WORKDIR /var/www/html +COPY . /var/www/html/ +RUN chown -R www-data:www-data /var/www/html + +EXPOSE 80 diff --git a/README_DEPLOY.md b/README_DEPLOY.md new file mode 100644 index 0000000..14828bc --- /dev/null +++ b/README_DEPLOY.md @@ -0,0 +1,72 @@ +# D1041231091 - WebGIS 01 sampai 05 + +Repository ini sudah disiapkan untuk deploy **sekali saja** menggunakan Docker Compose. + +## Isi Project + +- `/` = landing page utama +- `/01/` = Tugas 01 WebGIS SPBU +- `/02/` = Tugas 02 WebGIS SPBU + polyline + polygon +- `/03/` = Tugas 03 WebGIS jalan dan parsil tanah +- `/04/` = Tugas 04 WebGIS SPBU dengan layer control +- `/05/` = Tugas 05 WebGIS Poverty Mapping + +## Akun awal Tugas 05 + +- Username: `superadmin` +- Password: `password` + +## Cara push ke Gitea + +```bat +cd C:\laragon\www\webgis-spbu\D1041231091-WebGIS-All +git init +git branch -M main +git add . +git commit -m "Deploy WebGIS 01-05 dengan Docker" +git remote add origin https://git.ifuntanhub.dev/requestha/D1041231091-WebGIS-All.git +git push -u origin main +``` + +Kalau remote sudah ada: + +```bat +git remote set-url origin https://git.ifuntanhub.dev/requestha/D1041231091-WebGIS-All.git +git push -u origin main +``` + +## Cara deploy di Coolify + +1. Buat resource baru. +2. Pilih **Public Repository**. +3. Isi repository URL Gitea. +4. Branch: `main`. +5. Build Pack: **Docker Compose**. +6. Docker Compose Location: `/docker-compose.yml`. +7. Setelah resource dibuat, masuk ke service `web`. +8. Isi domain pada service `web`, contoh: + +```text +http://d1041231091-requestha-webgisall.203.24.51.230.sslip.io +``` + +9. Klik **Deploy** satu kali. +10. Tunggu sampai `web` dan `db` running. + +## Link setelah deploy + +```text +http://domain-kamu/ +http://domain-kamu/01/ +http://domain-kamu/02/ +http://domain-kamu/03/ +http://domain-kamu/04/ +http://domain-kamu/05/ +``` + +## Catatan penting + +- Jangan pilih menu database terpisah di Coolify. Database sudah ada di `docker-compose.yml`. +- Jangan pakai Docker Compose Empty jika kode belum ada di server. Pakai Public Repository agar Coolify build langsung dari Gitea. +- Jangan klik Deploy berkali-kali. Kalau Bad Gateway, cek log `web` dulu. +- Database otomatis diimport dari folder `db/init/` saat container `db` pertama kali dibuat. diff --git a/db/Dockerfile b/db/Dockerfile new file mode 100644 index 0000000..697abcd --- /dev/null +++ b/db/Dockerfile @@ -0,0 +1,2 @@ +FROM mysql:8.4 +COPY init/ /docker-entrypoint-initdb.d/ diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..f0b58e5 --- /dev/null +++ b/db/README.md @@ -0,0 +1,2 @@ +Folder ini berisi Dockerfile database dan SQL init. +MySQL akan menjalankan file di `init/` otomatis saat container pertama kali dibuat. diff --git a/db/init/10_01_spbudb.sql b/db/init/10_01_spbudb.sql new file mode 100644 index 0000000..2b6a562 --- /dev/null +++ b/db/init/10_01_spbudb.sql @@ -0,0 +1,82 @@ +CREATE DATABASE IF NOT EXISTS `01_spbudb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `01_spbudb`; +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `01_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') NOT NULL, + `buka_24_jam` enum('ya','tidak') NOT NULL, + `status_operasional` enum('buka','tutup') NOT NULL, + `nomor_hp` varchar(20) NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(1, 'SPBU IMBON', 'ramai', 'ya', 'buka', '0815497422156', -0.0469843, 109.354544, '2026-06-08 02:48:42', '2026-06-08 02:48:42'), +(3, 'SPBU Gertak 1', 'ramai', 'tidak', 'buka', '0815497422156', -0.0471444, 109.3556786, '2026-06-08 02:53:40', '2026-06-08 02:53:40'), +(4, 'SPBU IMBON', 'ramai', 'ya', 'tutup', '0815497422156', -0.0472983, 109.3563867, '2026-06-08 02:54:03', '2026-06-08 02:54:03'), +(5, 'SPBU IMBON', 'tidak_ramai', 'tidak', 'buka', '0815497422156', -0.0476848, 109.3552065, '2026-06-08 02:54:20', '2026-06-08 02:54:50'), +(6, 'SPBU Gertak 2', 'tidak_ramai', 'ya', 'buka', '087894587965', -0.0471113, 109.3553186, '2026-06-08 07:32:27', '2026-06-08 07:32:27'), +(7, 'SPBU OSO', 'tidak_ramai', 'tidak', 'buka', '026598745234', -0.0449249, 109.3367475, '2026-06-08 15:38:19', '2026-06-08 15:38:19'), +(8, 'SPBU GOLDEN TULIP', 'ramai', 'tidak', 'buka', '0564123', -0.0343809, 109.3319061, '2026-06-08 15:39:22', '2026-06-08 15:39:22'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/db/init/20_02_spbudb.sql b/db/init/20_02_spbudb.sql new file mode 100644 index 0000000..20d9c35 --- /dev/null +++ b/db/init/20_02_spbudb.sql @@ -0,0 +1,146 @@ +CREATE DATABASE IF NOT EXISTS `02_spbudb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `02_spbudb`; +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `02_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `polygon` +-- + +CREATE TABLE `polygon` ( + `id` int NOT NULL, + `nama_polygon` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `warna` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#22c55e', + `koordinat` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `polygon` +-- + +INSERT INTO `polygon` (`id`, `nama_polygon`, `keterangan`, `warna`, `koordinat`, `created_at`, `updated_at`) VALUES +(1, 'Rumah A', 'Rumah A di tepi jalan imbon', '#c42170', '[[-0.04619857363388995,109.35349255800249],[-0.04609122197957324,109.35370445251466],[-0.046228095338798736,109.35379564762117],[-0.04634349836696752,109.35357302427295]]', '2026-06-08 09:59:46', '2026-06-08 09:59:46'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `polyline` +-- + +CREATE TABLE `polyline` ( + `id` int NOT NULL, + `nama_line` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `warna` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#3b82f6', + `koordinat` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `polyline` +-- + +INSERT INTO `polyline` (`id`, `nama_line`, `keterangan`, `warna`, `koordinat`, `created_at`, `updated_at`) VALUES +(1, 'Jl Tanjung Sari', 'Jalan tembusan \'TanSar\", dari mulut di ImBon dan dari mulut Jl. Jend. Ayani', '#3b82f6', '[[-0.052839376622127024,109.34751927852632],[-0.05137402667155636,109.34858143329622],[-0.050509845915652725,109.34916079044343],[-0.04923236129902831,109.35004591941835],[-0.04847016458323742,109.35060918331146],[-0.04772943818927965,109.35115635395051],[-0.04757377829389879,109.35129046440126],[-0.047589881041730155,109.35139775276186],[-0.04771333544149917,109.35161232948305],[-0.04650562934709907,109.3523955345154],[-0.045646816111789314,109.35296952724458],[-0.0457756380977098,109.35312509536745]]', '2026-06-08 10:03:20', '2026-06-08 10:03:20'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'tidak_ramai', + `buka_24_jam` enum('ya','tidak') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'tidak', + `status_operasional` enum('buka','tutup') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'buka', + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(4, 'SPBU Imbon', 'ramai', 'ya', 'buka', '0812345678913', -0.046981865484009226, 109.35455471277238, '2026-06-08 09:58:55', '2026-06-08 09:58:55'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `polygon` +-- +ALTER TABLE `polygon` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `polyline` +-- +ALTER TABLE `polyline` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `polygon` +-- +ALTER TABLE `polygon` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `polyline` +-- +ALTER TABLE `polyline` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/db/init/30_03_webgisdb.sql b/db/init/30_03_webgisdb.sql new file mode 100644 index 0000000..00517ae --- /dev/null +++ b/db/init/30_03_webgisdb.sql @@ -0,0 +1,109 @@ +CREATE DATABASE IF NOT EXISTS `03_webgisdb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `03_webgisdb`; +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `03_webgisdb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `jalan` +-- + +CREATE TABLE `jalan` ( + `id` int NOT NULL, + `nama_jalan` varchar(150) NOT NULL, + `status_jalan` enum('nasional','provinsi','kabupaten') NOT NULL, + `panjang_meter` double NOT NULL, + `geojson` longtext NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `jalan` +-- + +INSERT INTO `jalan` (`id`, `nama_jalan`, `status_jalan`, `panjang_meter`, `geojson`, `created_at`, `updated_at`) VALUES +(1, 'Gg. Pak Cik', 'kabupaten', 169.77, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[109.34135,-0.055498],[109.342616,-0.056351]]}}', '2026-06-08 12:30:39', '2026-06-08 12:30:39'), +(2, 'Jl. Trans Kalimantan', 'nasional', 5544.23, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[109.374583,-0.069933],[109.375999,-0.068258],[109.379303,-0.064651],[109.380591,-0.063685],[109.381771,-0.062891],[109.383423,-0.061517],[109.385548,-0.059992],[109.386578,-0.059327],[109.387114,-0.059048],[109.387758,-0.058833],[109.38868,-0.058318],[109.389496,-0.058038],[109.390697,-0.05748],[109.391813,-0.056815],[109.393015,-0.056128],[109.394302,-0.055677],[109.39559,-0.055226],[109.396598,-0.054882],[109.397478,-0.054603],[109.398851,-0.054324],[109.401062,-0.053723],[109.401855,-0.053465],[109.402649,-0.052907],[109.403057,-0.052284],[109.403958,-0.049257],[109.405246,-0.044942],[109.406469,-0.041313],[109.406598,-0.04054],[109.406598,-0.039338],[109.406254,-0.038694],[109.406254,-0.038221],[109.406641,-0.038178],[109.406984,-0.038565]]}}', '2026-06-08 12:34:53', '2026-06-08 12:34:53'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `parsil_tanah` +-- + +CREATE TABLE `parsil_tanah` ( + `id` int NOT NULL, + `nama_parsil` varchar(150) NOT NULL, + `status_kepemilikan` enum('SHM','HGB','HGU','HP') NOT NULL, + `luas_meter` double NOT NULL, + `geojson` longtext NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Dumping data for table `parsil_tanah` +-- + +INSERT INTO `parsil_tanah` (`id`, `nama_parsil`, `status_kepemilikan`, `luas_meter`, `geojson`, `created_at`, `updated_at`) VALUES +(1, 'kavling A1', 'HP', 1239.31, '{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[109.339339,-0.060625],[109.339548,-0.060442],[109.339803,-0.060689],[109.339575,-0.060877],[109.339339,-0.060625]]]}}', '2026-06-08 12:30:03', '2026-06-08 12:30:03'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `jalan` +-- +ALTER TABLE `jalan` + ADD PRIMARY KEY (`id`); + +-- +-- Indexes for table `parsil_tanah` +-- +ALTER TABLE `parsil_tanah` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `jalan` +-- +ALTER TABLE `jalan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + +-- +-- AUTO_INCREMENT for table `parsil_tanah` +-- +ALTER TABLE `parsil_tanah` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/db/init/40_04_spbudb.sql b/db/init/40_04_spbudb.sql new file mode 100644 index 0000000..211376b --- /dev/null +++ b/db/init/40_04_spbudb.sql @@ -0,0 +1,80 @@ +CREATE DATABASE IF NOT EXISTS `04_spbudb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `04_spbudb`; +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 11, 2026 at 11:13 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `04_spbudb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `spbu` +-- + +CREATE TABLE `spbu` ( + `id` int NOT NULL, + `nama_spbu` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `status_ramai` enum('ramai','tidak_ramai') COLLATE utf8mb4_unicode_ci NOT NULL, + `buka_24_jam` enum('ya','tidak') COLLATE utf8mb4_unicode_ci NOT NULL, + `status_operasional` enum('buka','tutup') COLLATE utf8mb4_unicode_ci NOT NULL, + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `spbu` +-- + +INSERT INTO `spbu` (`id`, `nama_spbu`, `status_ramai`, `buka_24_jam`, `status_operasional`, `nomor_hp`, `latitude`, `longitude`, `created_at`, `updated_at`) VALUES +(1, 'SPBU Gertak 1', 'tidak_ramai', 'ya', 'buka', '0815497422156', -0.0551481, 109.3480718, '2026-06-09 06:06:31', '2026-06-09 06:06:31'), +(2, 'SPBU Gertak 1', 'ramai', 'ya', 'buka', '0815497422156', -0.0548271, 109.3477392, '2026-06-09 06:06:52', '2026-06-09 06:07:09'), +(3, 'SPBU OSO', 'ramai', 'tidak', 'buka', '0815497422156', -0.0551277, 109.3474925, '2026-06-09 06:07:35', '2026-06-09 06:07:35'), +(4, 'SPBU OSO', 'tidak_ramai', 'tidak', 'buka', '087894587965', -0.0552458, 109.3478036, '2026-06-09 06:07:54', '2026-06-09 06:07:54'), +(5, 'SPBU Gertak 2', 'ramai', 'ya', 'tutup', '0815497422156', -0.0554175, 109.3474495, '2026-06-09 06:08:15', '2026-06-09 06:08:15'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `spbu` +-- +ALTER TABLE `spbu` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `spbu` +-- +ALTER TABLE `spbu` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/db/init/50_05_povertydb.sql b/db/init/50_05_povertydb.sql new file mode 100644 index 0000000..f8ce3f7 --- /dev/null +++ b/db/init/50_05_povertydb.sql @@ -0,0 +1,378 @@ +CREATE DATABASE IF NOT EXISTS `05_povertydb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE `05_povertydb`; +SET FOREIGN_KEY_CHECKS=0; +-- phpMyAdmin SQL Dump +-- version 5.2.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Generation Time: Jun 12, 2026 at 10:30 AM +-- Server version: 8.4.3 +-- PHP Version: 8.3.30 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `05_povertydb` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `penerima_bantuan` +-- + +CREATE TABLE `penerima_bantuan` ( + `id` int NOT NULL, + `user_id` int DEFAULT NULL, + `rumah_ibadah_id` int DEFAULT NULL, + `nama_penerima` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `nik` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `no_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `alamat` text COLLATE utf8mb4_unicode_ci NOT NULL, + `pekerjaan` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `penghasilan` int NOT NULL, + `jumlah_tanggungan` int NOT NULL, + `kondisi_rumah` enum('baik','sedang','rusak') COLLATE utf8mb4_unicode_ci NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `jarak_meter` double DEFAULT NULL, + `status_pengajuan` enum('menunggu','diterima','ditolak','dibatalkan') COLLATE utf8mb4_unicode_ci DEFAULT 'menunggu', + `status_hapus` enum('normal','menunggu','disetujui','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'normal', + `alasan_hapus` text COLLATE utf8mb4_unicode_ci, + `alasan_status` text COLLATE utf8mb4_unicode_ci, + `sumber_pengajuan` enum('mandiri','admin_rumah_ibadah') COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `penerima_bantuan` +-- + +INSERT INTO `penerima_bantuan` (`id`, `user_id`, `rumah_ibadah_id`, `nama_penerima`, `nik`, `no_hp`, `alamat`, `pekerjaan`, `penghasilan`, `jumlah_tanggungan`, `kondisi_rumah`, `jenis_bantuan`, `latitude`, `longitude`, `jarak_meter`, `status_pengajuan`, `status_hapus`, `alasan_hapus`, `alasan_status`, `sumber_pengajuan`, `created_at`, `updated_at`) VALUES +(1, 4, 1, 'Jotok', '60103011704940005', '08567891325', 'Jl. Bujang Malaka, RT/RW 024/008, Beringin, Kapuas, Sanggau', 'Tukang Antar Koran', 500000, 2, 'sedang', 'sembako', -0.07088082929060342, 109.36113953590394, 160.17, 'diterima', 'normal', NULL, '', 'mandiri', '2026-06-09 10:10:05', '2026-06-09 11:56:27'), +(2, 6, 5, 'caca', '124854', '410154', 'Gg. Kasturi Raja, Jl. Kenari', 'Mahasiswa', 0, 0, 'baik', 'uang_tunai', -0.020854501391015294, 109.33250427246095, 259.51, 'diterima', 'normal', NULL, '', 'mandiri', '2026-06-09 10:57:26', '2026-06-09 14:53:12'), +(4, NULL, 2, 'angel1', '123456', '123456', '1234', 'wwwwww', 0, 0, 'sedang', 'uang_tunai', -0.025105475576032914, 109.33623790740968, 128.27, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 11:27:12', '2026-06-09 11:28:31'), +(5, NULL, 2, 'angel2', '123', '123', 'ssss', 'sssss', 0, 0, 'sedang', 'uang_tunai', -0.025245714451433383, 109.33630228042604, 122.41, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 11:28:22', '2026-06-09 11:28:29'), +(6, 4, 1, 'k', 'g', 'u', 'k', 'i', 5, 7, 'baik', 'zakat', -0.06864918884517689, 109.3591332435608, 218.94, 'dibatalkan', 'disetujui', 'h', 'h', 'admin_rumah_ibadah', '2026-06-09 11:57:59', '2026-06-09 12:19:58'), +(7, 9, 5, 'penerima03', '124854', '123', 'Gang Merak II, Mariana, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', 'Mahasiswa', 50000, 1, 'sedang', 'sembako', -0.02033240043514327, 109.33441936969759, 136.81, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-09 14:52:40', '2026-06-09 14:53:12'), +(8, 10, 1, 'penerima04', '222223432', '13334341', 'Komplek Mitra Indah Utama I, Bangka Belitung Darat, Pontianak Tenggara, Pontianak, Kalimantan Barat, Kalimantan, 78124, Indonesia', 'wiraswasta', 1000000, 3, 'baik', 'sembako', -0.06962094072669782, 109.35720741748811, 299.36, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-10 01:22:26', '2026-06-10 01:22:34'), +(9, 11, 1, 'penerima05', '111111111111111', '11111111111111111', 'Nilam Permai, Sungai Raya, Kubu Raya, Kalimantan Barat, Kalimantan, 78124, Indonesia', 'disabilitas', 0, 1, 'rusak', 'uang_tunai', -0.07270815709759908, 109.3613098561764, 299.07, 'diterima', 'normal', NULL, '', 'admin_rumah_ibadah', '2026-06-10 01:25:25', '2026-06-10 01:25:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `penyaluran_bantuan` +-- + +CREATE TABLE `penyaluran_bantuan` ( + `id` int NOT NULL, + `program_id` int DEFAULT NULL, + `penerima_id` int NOT NULL, + `rumah_ibadah_id` int NOT NULL, + `admin_id` int NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `nominal` int DEFAULT NULL, + `jumlah_item` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `bantuan_ke` int DEFAULT NULL, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `tanggal_penyaluran` date NOT NULL, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `penyaluran_bantuan` +-- + +INSERT INTO `penyaluran_bantuan` (`id`, `program_id`, `penerima_id`, `rumah_ibadah_id`, `admin_id`, `jenis_bantuan`, `nominal`, `jumlah_item`, `bantuan_ke`, `keterangan`, `tanggal_penyaluran`, `created_at`) VALUES +(1, 5, 5, 2, 5, 'zakat', 10000, NULL, 1, 'gg', '2026-06-09', '2026-06-09 11:33:36'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `program_bantuan` +-- + +CREATE TABLE `program_bantuan` ( + `id` int NOT NULL, + `rumah_ibadah_id` int DEFAULT NULL, + `nama_program` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL, + `jenis_bantuan` enum('zakat','sembako','uang_tunai','lainnya') COLLATE utf8mb4_unicode_ci NOT NULL, + `deskripsi` text COLLATE utf8mb4_unicode_ci, + `bulan` int NOT NULL, + `tahun` int NOT NULL, + `target_frekuensi` int DEFAULT '1', + `nominal` int DEFAULT NULL, + `jumlah_item` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `status_program` enum('aktif','selesai','dibatalkan') COLLATE utf8mb4_unicode_ci DEFAULT 'aktif', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `program_bantuan` +-- + +INSERT INTO `program_bantuan` (`id`, `rumah_ibadah_id`, `nama_program`, `jenis_bantuan`, `deskripsi`, `bulan`, `tahun`, `target_frekuensi`, `nominal`, `jumlah_item`, `status_program`, `created_at`, `updated_at`) VALUES +(3, 2, 'a', 'uang_tunai', 'a', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:29:28', '2026-06-09 11:29:28'), +(4, 2, 'b', 'uang_tunai', 'b', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:29:47', '2026-06-09 11:29:47'), +(5, 2, 'c', 'sembako', 'c', 6, 2026, 1, NULL, NULL, 'aktif', '2026-06-09 11:30:54', '2026-06-09 11:30:54'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `riwayat_status` +-- + +CREATE TABLE `riwayat_status` ( + `id` int NOT NULL, + `penerima_id` int NOT NULL, + `user_id` int NOT NULL, + `status_lama` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `status_baru` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `catatan` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `riwayat_status` +-- + +INSERT INTO `riwayat_status` (`id`, `penerima_id`, `user_id`, `status_lama`, `status_baru`, `catatan`, `created_at`) VALUES +(1, 1, 4, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 10:10:05'), +(2, 1, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 10:48:16'), +(3, 2, 6, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 10:57:26'), +(4, 2, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 10:59:04'), +(7, 4, 5, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:27:12'), +(8, 5, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:28:22'), +(9, 5, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:28:29'), +(10, 4, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:28:31'), +(11, 6, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 11:57:59'), +(12, 6, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 11:58:09'), +(13, 6, 1, 'diterima', 'dibatalkan', 'jotok 2 rmh', '2026-06-09 11:58:22'), +(14, 6, 1, 'dibatalkan', 'hapus_disetujui', 'h', '2026-06-09 12:19:58'), +(15, 7, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-09 14:52:40'), +(16, 7, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-09 14:52:49'), +(17, 8, 1, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-10 01:22:26'), +(18, 8, 1, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-10 01:22:34'), +(19, 9, 2, NULL, 'menunggu', 'Pengajuan penerima dibuat.', '2026-06-10 01:25:25'), +(20, 9, 2, 'menunggu', 'diterima', 'Status penerima diubah.', '2026-06-10 01:25:37'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `rumah_ibadah` +-- + +CREATE TABLE `rumah_ibadah` ( + `id` int NOT NULL, + `user_id` int DEFAULT NULL, + `nama_rumah_ibadah` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL, + `agama` enum('islam','kristen','katolik','hindu','buddha','konghucu') COLLATE utf8mb4_unicode_ci NOT NULL, + `alamat` text COLLATE utf8mb4_unicode_ci NOT NULL, + `nomor_hp` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, + `latitude` double NOT NULL, + `longitude` double NOT NULL, + `status_pengajuan` enum('menunggu','diterima','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'menunggu', + `status_hapus` enum('normal','menunggu','disetujui','ditolak') COLLATE utf8mb4_unicode_ci DEFAULT 'normal', + `alasan_hapus` text COLLATE utf8mb4_unicode_ci, + `keterangan` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `rumah_ibadah` +-- + +INSERT INTO `rumah_ibadah` (`id`, `user_id`, `nama_rumah_ibadah`, `agama`, `alamat`, `nomor_hp`, `latitude`, `longitude`, `status_pengajuan`, `status_hapus`, `alasan_hapus`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 2, 'Gereja Katolik Paroki St. Sesilia', 'katolik', 'Jl. Sungai Raya Dalam', '0812345678913', -0.07052077848657445, 109.3597447872162, 'diterima', 'normal', NULL, '', '2026-06-09 10:11:48', '2026-06-09 10:48:18'), +(2, 5, 'GPIB', 'kristen', 'Jl. Kartini, Pontianak', '0812345678913', -0.02508443513561694, 109.33739125728609, 'diterima', 'normal', NULL, '', '2026-06-09 10:54:13', '2026-06-09 10:59:11'), +(4, 8, 'Gereja Elim', 'kristen', 'Jalan Rajawali, Tengah, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', '0812345678913', -0.02152069970142388, 109.33472514152527, 'ditolak', 'disetujui', 's', 's', '2026-06-09 14:50:50', '2026-06-09 14:51:16'), +(5, 8, 'Gereja Elim', 'kristen', 'Jalan Rajawali, Tengah, Pontianak Kota, Pontianak, Kalimantan Barat, Kalimantan, 78112, Indonesia', '0812345678913', -0.02151988492191236, 109.33474123477937, 'diterima', 'normal', NULL, 'Ditambahkan langsung oleh super admin.', '2026-06-09 14:53:12', '2026-06-09 14:53:12'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `users` +-- + +CREATE TABLE `users` ( + `id` int NOT NULL, + `nama` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `role` enum('super_admin','admin_rumah_ibadah','penerima') COLLATE utf8mb4_unicode_ci NOT NULL, + `status_akun` enum('aktif','nonaktif') COLLATE utf8mb4_unicode_ci DEFAULT 'aktif', + `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data for table `users` +-- + +INSERT INTO `users` (`id`, `nama`, `username`, `password`, `role`, `status_akun`, `created_at`, `updated_at`) VALUES +(1, 'Super Admin Dinas Sosial', 'superadmin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'super_admin', 'aktif', '2026-06-09 10:04:24', '2026-06-09 14:48:23'), +(2, 'admingerejasesilia', 'admingerejasesilia', '$2y$10$Pt4VTj7yDLt1xabM5WwGJuBo.gat807fkV0Q.KiGKv.V0tRbo1SHW', 'admin_rumah_ibadah', 'aktif', '2026-06-09 10:06:38', '2026-06-10 01:23:46'), +(4, 'penerima01', 'penerima01', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'penerima', 'nonaktif', '2026-06-09 10:08:28', '2026-06-10 01:18:35'), +(5, 'admingpib', 'admingpib', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'admin_rumah_ibadah', 'nonaktif', '2026-06-09 10:52:35', '2026-06-09 14:47:58'), +(6, 'penerima02', 'penerima02', '$2y$12$79Vc.m66x5uIIIt4rifL0OIY3qrzzXmgktTW3Y1QEwWB9GMaeLFni', 'penerima', 'aktif', '2026-06-09 10:55:15', '2026-06-09 14:47:48'), +(8, 'Admin Gereja Elim', 'admingerejaelim', '$2y$10$hiPcHEFAb2lPq3WKLra1MuZ8220s/UY.3PG24ZIrmbxrbp2imboGO', 'admin_rumah_ibadah', 'aktif', '2026-06-09 14:49:55', '2026-06-09 14:49:55'), +(9, 'penerima03', 'penerima03', '$2y$10$.50kERk9OSPvJhe.9IFxd.3qi2j5spTxtM36Kx1mI7Q3HkeVlyzvm', 'penerima', 'nonaktif', '2026-06-09 14:51:50', '2026-06-10 01:18:20'), +(10, 'penerima04', 'penerima04', '$2y$10$Q0yfgX7/KnCa4sxnz/f5YupK2aVyKWLDPO8CD/mdQlCSOTRbzLBli', 'penerima', 'aktif', '2026-06-10 01:20:04', '2026-06-10 01:20:04'), +(11, 'penerima05', 'penerima05', '$2y$10$0UJ97IYsDsS/K/qJbHJsiO938TFSU2AorU0fqxAawnbpwhJN/qBqi', 'penerima', 'aktif', '2026-06-10 01:21:01', '2026-06-10 01:21:01'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_pb_user` (`user_id`), + ADD KEY `idx_pb_ri` (`rumah_ibadah_id`), + ADD KEY `idx_pb_status` (`status_pengajuan`), + ADD KEY `idx_pb_jenis` (`jenis_bantuan`); + +-- +-- Indexes for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_pen_program` (`program_id`), + ADD KEY `idx_pen_penerima` (`penerima_id`), + ADD KEY `idx_pen_ri` (`rumah_ibadah_id`), + ADD KEY `idx_pen_admin` (`admin_id`), + ADD KEY `idx_pen_tanggal` (`tanggal_penyaluran`); + +-- +-- Indexes for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_prog_ri` (`rumah_ibadah_id`), + ADD KEY `idx_prog_periode` (`bulan`,`tahun`), + ADD KEY `idx_prog_status` (`status_program`); + +-- +-- Indexes for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_rw_penerima` (`penerima_id`), + ADD KEY `idx_rw_user` (`user_id`); + +-- +-- Indexes for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + ADD PRIMARY KEY (`id`), + ADD KEY `idx_ri_user` (`user_id`), + ADD KEY `idx_ri_status` (`status_pengajuan`), + ADD KEY `idx_ri_agama` (`agama`); + +-- +-- Indexes for table `users` +-- +ALTER TABLE `users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `username` (`username`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + +-- +-- AUTO_INCREMENT for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- AUTO_INCREMENT for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- AUTO_INCREMENT for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; + +-- +-- AUTO_INCREMENT for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; + +-- +-- AUTO_INCREMENT for table `users` +-- +ALTER TABLE `users` + MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `penerima_bantuan` +-- +ALTER TABLE `penerima_bantuan` + ADD CONSTRAINT `fk_pb_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pb_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- +-- Constraints for table `penyaluran_bantuan` +-- +ALTER TABLE `penyaluran_bantuan` + ADD CONSTRAINT `fk_pen_admin` FOREIGN KEY (`admin_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_penerima` FOREIGN KEY (`penerima_id`) REFERENCES `penerima_bantuan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_program` FOREIGN KEY (`program_id`) REFERENCES `program_bantuan` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + ADD CONSTRAINT `fk_pen_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `program_bantuan` +-- +ALTER TABLE `program_bantuan` + ADD CONSTRAINT `fk_prog_ri` FOREIGN KEY (`rumah_ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- +-- Constraints for table `riwayat_status` +-- +ALTER TABLE `riwayat_status` + ADD CONSTRAINT `fk_rw_penerima` FOREIGN KEY (`penerima_id`) REFERENCES `penerima_bantuan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk_rw_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `rumah_ibadah` +-- +ALTER TABLE `rumah_ibadah` + ADD CONSTRAINT `fk_ri_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + +SET FOREIGN_KEY_CHECKS=1; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b0cd978 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + web: + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + depends_on: + - db + ports: + - "80" + environment: + DB_HOST: db + DB_USER: root + DB_PASSWORD: webgisidolapass + + db: + build: + context: ./db + dockerfile: Dockerfile + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: webgisidolapass + MYSQL_ROOT_HOST: "%" + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -pwebgisidolapass --silent"] + interval: 10s + timeout: 5s + retries: 10 diff --git a/index.php b/index.php new file mode 100644 index 0000000..82bcdb2 --- /dev/null +++ b/index.php @@ -0,0 +1,85 @@ + '01', 'title' => 'Tugas 01', 'subtitle' => 'Manajemen SPBU', 'desc' => 'CRUD titik SPBU dengan status ramai, jam operasional, buka/tutup, nomor HP, dan visualisasi Leaflet.', 'url' => '01/', 'badge' => 'SPBU'], + ['id' => '02', 'title' => 'Tugas 02', 'subtitle' => 'SPBU + Polyline + Polygon', 'desc' => 'Pemetaan SPBU, garis jalan, dan area polygon dengan penyimpanan data MySQL.', 'url' => '02/', 'badge' => 'Layer'], + ['id' => '03', 'title' => 'Tugas 03', 'subtitle' => 'Manajemen Jalan & Parsil Tanah', 'desc' => 'WebGIS untuk data jalan dan parsil tanah menggunakan Leaflet dan database geospasial sederhana.', 'url' => '03/', 'badge' => 'Jalan'], + ['id' => '04', 'title' => 'Tugas 04', 'subtitle' => 'SPBU dengan Layer Groups & Layer Control', 'desc' => 'Pengelompokan layer, kontrol tampilan peta, serta manajemen titik SPBU berbasis PHP native.', 'url' => '04/', 'badge' => 'Control'], + ['id' => '05', 'title' => 'Tugas 05', 'subtitle' => 'WebGIS Poverty Mapping', 'desc' => 'Aplikasi bantuan/zakat berbasis lokasi dengan role super admin, admin rumah ibadah, dan penerima.', 'url' => '05/', 'badge' => 'Poverty'] +]; +?> + + + + + + D1041231091 - Kumpulan Tugas WebGIS + + + +
+
+
+
🗺️ Kumpulan Deploy WebGIS
+

D1041231091
Requestha Amorine Montass

+

Landing page untuk mengakses seluruh tugas WebGIS 01 sampai 05 dalam satu deployment Docker. Setiap kartu di bawah membuka aplikasi sesuai folder tugas.

+ +
+
+
MahasiswaRequestha Amorine Montass
+
NIMD1041231091
+
TeknologiPHP Native, MySQL, Leaflet.js, Docker
+
+
+ +
+ + +
+
+
+
+
+

+

+

+
+
Buka aplikasi
+
+ +
+ +
+ +