Initial commit: Setup Landing Page dan file pertemuan

This commit is contained in:
2026-06-09 17:46:57 +07:00
commit a62e01e932
35 changed files with 8807 additions and 0 deletions
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = $_POST['id'];
$tabel = $_POST['tabel'];
// Daftar tabel yang diizinkan (untuk keamanan)
$allowed_tables = ['spbu_locations', 'jalan', 'parsil', 'rumah_ibadah', 'penduduk_miskin'];
if (in_array($tabel, $allowed_tables)) {
$stmt = $conn->prepare("DELETE FROM $tabel WHERE id = ?");
$stmt->bind_param("i", $id); // "i" karena ID sekarang adalah Integer
if ($stmt->execute()) {
echo "Data berhasil dihapus!";
} else {
echo "Gagal menghapus: " . $conn->error;
}
$stmt->close();
} else {
echo "Tabel tidak valid!";
}
$conn->close();
}
?>
+29
View File
@@ -0,0 +1,29 @@
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$lat = isset($_GET['lat']) ? floatval($_GET['lat']) : 0;
$lng = isset($_GET['lng']) ? floatval($_GET['lng']) : 0;
if (!$lat || !$lng) {
echo json_encode(['error' => 'Koordinat tidak valid']);
exit;
}
$url = "https://nominatim.openstreetmap.org/reverse?format=json&lat={$lat}&lon={$lng}&accept-language=id&zoom=18";
$context = stream_context_create([
'http' => [
'header' => "User-Agent: WebGIS-Pontianak/1.0\r\n",
'timeout' => 10
]
]);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
echo json_encode(['error' => 'Gagal menghubungi Nominatim']);
exit;
}
echo $response;
+477
View File
@@ -0,0 +1,477 @@
<?php
session_start();
// Mengecek apakah user yang mengakses adalah admin/operator
$isAdmin = isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistem WebGIS Terpadu — Kota Pontianak</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="style.css">
<style>
</style>
</head>
<body>
<!-- ==============================
HEADER BAR
============================== -->
<div class="webgis-header">
<div class="header-brand">
<div class="brand-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
</div>
<div>
<div class="brand-name">WebGIS Terpadu</div>
<div class="brand-subtitle">Kota Pontianak</div>
</div>
</div>
<div class="header-divider"></div>
<!-- SEARCH BAR -->
<div class="search-wrapper">
<div class="search-bar">
<!-- Category Selector -->
<!-- Category Selector Custom -->
<div class="search-category-wrap" id="custom-select-wrap">
<!-- Ikon Garis Tiga (Asli) -->
<span class="search-category-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 10h16M4 14h8" />
</svg>
</span>
<!-- Teks yang tampil -->
<div class="custom-select-trigger" id="custom-select-trigger">Semua Layer</div>
<input type="hidden" id="search-category" value="Semua">
<!-- Ikon Panah Bawah (Asli) -->
<span class="category-arrow">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</span>
<!-- Kotak Pilihan Dropdown yang Luas -->
<div class="custom-select-options" id="custom-select-options">
<div class="custom-option selected" data-value="Semua">Semua Layer</div>
<div class="custom-option" data-value="SPBU">SPBU</div>
<div class="custom-option" data-value="Jalan">Jaringan Jalan</div>
<div class="custom-option" data-value="Parsil">Parsil / Lahan</div>
<div class="custom-option" data-value="Rumah Ibadah">Rumah Ibadah</div>
</div>
</div>
<!-- Search Input -->
<div class="search-input-wrap">
<span class="search-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</span>
<input type="text" id="search-input" placeholder="Cari nama lokasi, jalan, atau lahan..."
onkeyup="jalankanPencarian()" oninput="toggleClearBtn()" autocomplete="off">
<button class="search-clear-btn" id="search-clear" onclick="clearSearch()" title="Hapus pencarian">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Dropdown Results -->
<div class="search-results" id="search-results"></div>
</div>
<!-- HEADER RIGHT TOOLS -->
<div class="header-tools">
<?php if ($isAdmin): ?>
<div class="user-profile-menu">
<button class="profile-trigger" id="profile-trigger" onclick="toggleProfileMenu(event)">
<img src="<?php echo htmlspecialchars($_SESSION['avatar']); ?>" class="profile-avatar" alt="Avatar">
<span class="profile-username"><?php echo htmlspecialchars($_SESSION['username']); ?></span>
<svg class="profile-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="profile-dropdown" id="profile-dropdown">
<div class="dropdown-user-info">
<div class="dropdown-username"><?php echo htmlspecialchars($_SESSION['username']); ?></div>
<div class="dropdown-role">Operator Wilayah</div>
</div>
<div class="dropdown-divider"></div>
<a href="logout.php" class="dropdown-item logout-link">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
<span>Logout</span>
</a>
</div>
</div>
<?php else: ?>
<a href="login.php" class="tool-btn auth-btn" title="Login Operator">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H7a3 3 0 01-3-3V7a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
</svg>
<span>Login</span>
</a>
<?php endif; ?>
<div style="width: 1px; height: 20px; background: rgba(255,255,255,0.1); margin: 0 4px;"></div>
<div class="tool-btn" title="Status Koneksi">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
</svg>
<div class="status-dot"></div>
</div>
<div class="tool-btn" title="Tentang Aplikasi">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
<!-- ==============================
SIDEBAR INPUT TERPUSAT
============================== -->
<!-- Toggle Button -->
<?php if ($isAdmin): ?>
<button class="sidebar-toggle" id="sidebar-toggle" title="Panel Input Data">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
</svg>
<span>Input Data</span>
</button>
<?php endif; ?>
<!-- Sidebar Panel -->
<div class="input-sidebar" id="input-sidebar">
<div class="sidebar-header">
<div class="sidebar-title">
<div class="sidebar-title-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div class="sidebar-title-text">Input Data</div>
<div class="sidebar-title-sub">Tambah data ke peta</div>
</div>
</div>
<button class="sidebar-close" id="sidebar-close" title="Tutup">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Tab Navigation -->
<div class="sidebar-tabs">
<button class="stab active" data-tab="spbu" onclick="switchSidebarTab('spbu')">
<span class="stab-icon">⛽</span>
<span>SPBU</span>
</button>
<button class="stab" data-tab="jalan" onclick="switchSidebarTab('jalan')">
<span class="stab-icon">🛣️</span>
<span>Jalan</span>
</button>
<button class="stab" data-tab="parsil" onclick="switchSidebarTab('parsil')">
<span class="stab-icon">🏘️</span>
<span>Parsil</span>
</button>
<button class="stab" data-tab="ibadah" onclick="switchSidebarTab('ibadah')">
<span class="stab-icon">🕌</span>
<span>Ibadah</span>
</button>
<button class="stab" data-tab="penduduk" onclick="switchSidebarTab('penduduk')">
<span class="stab-icon">👥</span>
<span>Penduduk</span>
</button>
</div>
<!-- Scrollable Content -->
<div class="sidebar-body">
<!-- ===== TAB: SPBU ===== -->
<div class="stab-content active" id="tab-spbu">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Tandai Lokasi</strong>, lalu klik titik di peta untuk menentukan posisi SPBU.
</p>
</div>
<button class="btn-pick-location" id="btn-pick-spbu" onclick="activatePickMode('spbu')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Tandai Lokasi di Peta
</button>
<div class="coords-display" id="coords-spbu">
<span class="coords-label">Koordinat:</span>
<span class="coords-value" id="coords-spbu-val">Belum dipilih</span>
</div>
<div class="step-guide" style="margin-top:14px;">
<div class="step-badge">Langkah 2</div>
<p>Isi detail informasi SPBU di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama SPBU <span class="required">*</span></label>
<input type="text" id="sb_nama_spbu" class="sinput" placeholder="Contoh: SPBU Jl. Ahmad Yani">
</div>
<div class="sfield">
<label class="sfield-label">Nomor Telepon</label>
<input type="text" id="sb_nohp_spbu" class="sinput" placeholder="Contoh: 0561-xxxxxx">
</div>
<div class="sfield">
<label class="sfield-label">Status Operasional</label>
<select id="sb_kategori_spbu" class="sselect">
<option value="Ya">Buka 24 Jam</option>
<option value="Tidak">Reguler</option>
</select>
</div>
<button class="btn-save-sidebar" onclick="saveSPBUSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan SPBU
</button>
</div>
<!-- ===== TAB: JALAN ===== -->
<div class="stab-content" id="tab-jalan">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Gambar Jalur</strong>, lalu klik titik-titik di peta membentuk ruas jalan.
Klik dua kali untuk selesai.</p>
</div>
<button class="btn-pick-location btn-draw" id="btn-draw-jalan" onclick="activateDrawMode('polyline')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
Gambar Jalur di Peta
</button>
<div class="coords-display" id="coords-jalan">
<span class="coords-label">Status:</span>
<span class="coords-value" id="coords-jalan-val">Belum digambar</span>
</div>
<div class="step-guide" style="margin-top:14px;">
<div class="step-badge">Langkah 2</div>
<p>Isi detail informasi jalan di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama Jalan <span class="required">*</span></label>
<input type="text" id="sb_nama_jalan" class="sinput" placeholder="Contoh: Jl. Gajah Mada">
</div>
<div class="sfield">
<label class="sfield-label">Status Jalan</label>
<select id="sb_status_jalan" class="sselect">
<option value="Jalan Nasional">Jalan Nasional</option>
<option value="Jalan Provinsi">Jalan Provinsi</option>
<option value="Jalan Kabupaten">Jalan Kabupaten</option>
</select>
</div>
<div class="sfield">
<label class="sfield-label">Panjang Terukur</label>
<input type="text" id="sb_panjang_jalan" class="sinput" placeholder="Otomatis terisi" readonly>
</div>
<button class="btn-save-sidebar btn-teal" onclick="saveJalanSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Jalan
</button>
</div>
<!-- ===== TAB: PARSIL ===== -->
<div class="stab-content" id="tab-parsil">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Gambar Parsil</strong>, lalu klik titik-titik untuk membentuk batas lahan.
Klik dua kali untuk selesai.</p>
</div>
<button class="btn-pick-location btn-purple" id="btn-draw-parsil" onclick="activateDrawMode('polygon')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
Gambar Area Parsil
</button>
<div class="coords-display" id="coords-parsil">
<span class="coords-label">Status:</span>
<span class="coords-value" id="coords-parsil-val">Belum digambar</span>
</div>
<div class="step-guide" style="margin-top:14px;">
<div class="step-badge">Langkah 2</div>
<p>Isi detail informasi lahan di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama Pemilik <span class="required">*</span></label>
<input type="text" id="sb_nama_parsil" class="sinput" placeholder="Contoh: Budi Santoso">
</div>
<div class="sfield">
<label class="sfield-label">Status Tanah</label>
<select id="sb_status_parsil" class="sselect">
<option value="SHM">SHM (Hak Milik)</option>
<option value="HGB">HGB (Guna Bangunan)</option>
<option value="HGU">HGU (Guna Usaha)</option>
<option value="HP">HP (Hak Pakai)</option>
</select>
</div>
<div class="sfield">
<label class="sfield-label">Luas Terukur</label>
<input type="text" id="sb_luas_parsil" class="sinput" placeholder="Otomatis terisi" readonly>
</div>
<button class="btn-save-sidebar btn-purple-solid" onclick="saveParsil_Sidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Parsil
</button>
</div>
<!-- ===== TAB: RUMAH IBADAH ===== -->
<div class="stab-content" id="tab-ibadah">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Tandai Lokasi</strong>, lalu klik titik di peta untuk menentukan posisi rumah
ibadah.</p>
</div>
<button class="btn-pick-location btn-amber" id="btn-pick-ibadah" onclick="activatePickMode('ibadah')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Tandai Lokasi di Peta
</button>
<div class="coords-display" id="coords-ibadah">
<span class="coords-label">Koordinat:</span>
<span class="coords-value" id="coords-ibadah-val">Belum dipilih</span>
</div>
<div class="step-guide" style="margin-top:14px;">
<div class="step-badge">Langkah 2</div>
<p>Isi nama rumah ibadah di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama Rumah Ibadah <span class="required">*</span></label>
<input type="text" id="sb_nama_ibadah" class="sinput" placeholder="Contoh: Masjid Raya Mujahidin">
</div>
<button class="btn-save-sidebar btn-amber-solid" onclick="saveRumahIbadahSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Rumah Ibadah
</button>
</div>
<!-- ===== TAB: PENDUDUK MISKIN ===== -->
<div class="stab-content" id="tab-penduduk">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Tandai Lokasi</strong>, lalu klik titik di peta untuk menentukan posisi
tempat tinggal.</p>
</div>
<button class="btn-pick-location btn-red" id="btn-pick-penduduk" onclick="activatePickMode('penduduk')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Tandai Lokasi di Peta
</button>
<div class="coords-display" id="coords-penduduk">
<span class="coords-label">Koordinat:</span>
<span class="coords-value" id="coords-penduduk-val">Belum dipilih</span>
</div>
<div class="step-guide" style="margin-top:14px;">
<div class="step-badge">Langkah 2</div>
<p>Isi alamat dan nama kepala keluarga di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Alamat Lengkap <span class="required">*</span></label>
<textarea id="sb_alamat_penduduk" class="sinput" rows="3"
placeholder="Contoh: Jl. Sungai Raya Dalam Gg. Ceria No. 12" style="resize:none;"></textarea>
</div>
<div class="sfield">
<label class="sfield-label">Nama Kepala Keluarga <span class="required">*</span></label>
<input type="text" id="sb_nama_penduduk" class="sinput" placeholder="Contoh: Siti Rahayu">
</div>
<button class="btn-save-sidebar btn-red-solid" onclick="savePendudukSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Penduduk
</button>
</div>
</div><!-- end sidebar-body -->
</div><!-- end input-sidebar -->
<!-- Overlay saat mode pilih lokasi aktif -->
<div class="pick-mode-overlay" id="pick-overlay">
<div class="pick-mode-banner">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5" />
</svg>
<span id="pick-overlay-text">Klik lokasi di peta...</span>
<button onclick="deactivatePickMode()">Batal</button>
</div>
</div>
<!-- MAP (DENGAN OFFSET HEADER) -->
<div id="map" style="padding-top: 56px; height: 100vh; box-sizing: border-box;"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet.heat@0.2.0/dist/leaflet-heat.js"></script>
<script>
// Variabel global untuk mendeteksi status admin di script.js
const IS_ADMIN = <?php echo $isAdmin ? 'true' : 'false'; ?>;
// Toggle dropdown menu profil pengguna
function toggleProfileMenu(e) {
e.stopPropagation();
var dropdown = document.getElementById('profile-dropdown');
if (dropdown) dropdown.classList.toggle('active');
}
// Tutup dropdown saat klik di luar area profil
document.addEventListener('click', function() {
var dropdown = document.getElementById('profile-dropdown');
if (dropdown) dropdown.classList.remove('active');
});
</script>
<script src="script.js"></script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
<?php
$host = "localhost";
$user = "root";
$pass = "";
$db = "webgis_db";
// Connect to MySQL server first
$conn = new mysqli($host, $user, $pass);
if ($conn->connect_error) {
die("Koneksi gagal: " . $conn->connect_error);
}
// Create database if not exists
$conn->query("CREATE DATABASE IF NOT EXISTS `$db` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");
// Select database
$conn->select_db($db);
// Create users table
$sql_users = "CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255) NOT NULL,
`role` VARCHAR(20) NOT NULL DEFAULT 'admin'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
if (!$conn->query($sql_users)) {
die("Gagal membuat tabel users: " . $conn->error);
}
// Seed default admin if empty
$check_empty = $conn->query("SELECT COUNT(*) as count FROM users");
$row = $check_empty->fetch_assoc();
if ($row['count'] == 0) {
$default_user = 'admin';
// Hashing password "admin123"
$default_pass = password_hash('admin123', PASSWORD_BCRYPT);
$default_avatar = 'https://ui-avatars.com/api/?name=Admin+Operator&background=1e40af&color=fff&bold=true&size=128';
$default_role = 'admin';
$stmt = $conn->prepare("INSERT INTO users (username, password, avatar, role) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssss", $default_user, $default_pass, $default_avatar, $default_role);
$stmt->execute();
$stmt->close();
}
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
include 'koneksi.php';
// Cek data apa yang diminta, jika kosong jadikan 'spbu' sebagai default
$jenis = isset($_GET['jenis']) ? $_GET['jenis'] : 'spbu';
if ($jenis == 'spbu') {
$sql = "SELECT * FROM spbu_locations";
} else if ($jenis == 'jalan') {
$sql = "SELECT * FROM jalan";
} else if ($jenis == 'parsil') {
$sql = "SELECT * FROM parsil";
} else if ($jenis == 'rumah_ibadah') {
$sql = "SELECT * FROM rumah_ibadah";
} else if ($jenis == 'penduduk_miskin') {
$sql = "SELECT * FROM penduduk_miskin";
} else {
die("Jenis data tidak ditemukan");
}
$result = $conn->query($sql);
$data = array();
if ($result && $result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$data[] = $row;
}
}
header('Content-Type: application/json');
echo json_encode($data);
$conn->close();
?>
+80
View File
@@ -0,0 +1,80 @@
<?php
session_start();
// Jika sudah login sebagai admin, tendang kembali ke index
if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
header("Location: index.php");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'];
$password = $_POST['password'];
include 'koneksi.php';
// Query user from database
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($result && $result->num_rows > 0) {
$user = $result->fetch_assoc();
// Check hashed password
if (password_verify($password, $user['password'])) {
$_SESSION['role'] = $user['role'];
$_SESSION['username'] = $user['username'];
$_SESSION['avatar'] = $user['avatar'];
header("Location: index.php");
exit;
}
}
$error = "Username atau password salah!";
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Operator - WebGIS Pontianak</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body class="login-body">
<div class="login-card">
<div class="login-logo">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
</div>
<h2>Login Operator</h2>
<p>Masuk untuk mengelola data WebGIS</p>
<?php if (isset($error)): ?>
<div class="error-msg"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST" class="login-form">
<div class="sfield">
<label class="sfield-label">Username</label>
<input type="text" name="username" class="sinput" placeholder="Masukkan username..." required autofocus>
</div>
<div class="sfield">
<label class="sfield-label">Password</label>
<input type="password" name="password" class="sinput" placeholder="Masukkan password..." required>
</div>
<button type="submit" class="btn btn-save" style="width: 100%; margin-top: 8px;">Login ke Sistem</button>
</form>
<a href="index.php" class="back-link">← Kembali ke Peta</a>
</div>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
<?php
session_start();
session_destroy(); // Menghapus semua sesi
header("Location: index.php");
exit;
+33
View File
@@ -0,0 +1,33 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = $_POST['nama_spbu'];
$hp = $_POST['no_hp'];
$kategori = $_POST['kategori'];
$lat = $_POST['latitude'];
$lng = $_POST['longitude'];
// Perhatikan: id dihilangkan, tanda tanya hanya 5
$stmt = $conn->prepare("INSERT INTO spbu_locations (nama_spbu, no_hp, kategori, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
// Perhatikan: huruf sssdd (5 huruf untuk 5 variabel)
$stmt->bind_param("sssdd", $nama, $hp, $kategori, $lat, $lng);
if ($stmt->execute()) {
echo "Data SPBU berhasil disimpan!";
} else {
echo "Gagal menyimpan data: " . $conn->error;
}
$stmt->close();
$conn->close();
}
+32
View File
@@ -0,0 +1,32 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = $_POST['nama_jalan'];
$status = $_POST['status'];
$panjang = $_POST['panjang'];
$koordinat = $_POST['koordinat'];
// Perhatikan: id dihilangkan, tanda tanya hanya 4
$stmt = $conn->prepare("INSERT INTO jalan (nama_jalan, status, panjang, koordinat) VALUES (?, ?, ?, ?)");
// Perhatikan: huruf ssds (4 huruf untuk 4 variabel)
$stmt->bind_param("ssds", $nama, $status, $panjang, $koordinat);
if ($stmt->execute()) {
echo "Data Jalan berhasil disimpan!";
} else {
echo "Gagal: " . $conn->error;
}
$stmt->close();
$conn->close();
}
+32
View File
@@ -0,0 +1,32 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = $_POST['nama_pemilik'];
$status = $_POST['status'];
$luas = $_POST['luas'];
$koordinat = $_POST['koordinat'];
// Perhatikan: id dihilangkan, tanda tanya hanya 4
$stmt = $conn->prepare("INSERT INTO parsil (nama_pemilik, status, luas, koordinat) VALUES (?, ?, ?, ?)");
// Perhatikan: huruf ssds (4 huruf untuk 4 variabel)
$stmt->bind_param("ssds", $nama, $status, $luas, $koordinat);
if ($stmt->execute()) {
echo "Data Parsil berhasil disimpan!";
} else {
echo "Gagal: " . $conn->error;
}
$stmt->close();
$conn->close();
}
+32
View File
@@ -0,0 +1,32 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
// Ambil data dari metode POST
$nama = $_POST['nama'];
$alamat = $_POST['alamat'];
$latitude = $_POST['latitude'];
$longitude = $_POST['longitude'];
// Gunakan Prepared Statement untuk mencegah SQL Injection
$query = $conn->prepare("INSERT INTO penduduk_miskin (nama_kk, alamat, lat, lng) VALUES (?, ?, ?, ?)");
$query->bind_param("ssdd", $nama, $alamat, $latitude, $longitude);
// Eksekusi dan kirim respon ke Javascript
if ($query->execute()) {
echo "Data Penduduk Miskin berhasil disimpan!";
} else {
echo "Gagal menyimpan data: " . $conn->error;
}
$query->close();
$conn->close();
?>
+31
View File
@@ -0,0 +1,31 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
// Ambil data dari metode POST
$nama = $_POST['nama'];
$latitude = $_POST['latitude'];
$longitude = $_POST['longitude'];
// Gunakan Prepared Statement untuk mencegah SQL Injection
$query = $conn->prepare("INSERT INTO rumah_ibadah (nama, lat, lng) VALUES (?, ?, ?)");
$query->bind_param("sdd", $nama, $latitude, $longitude);
// Eksekusi dan kirim respon ke Javascript
if ($query->execute()) {
echo "Data Rumah Ibadah berhasil disimpan!";
} else {
echo "Gagal menyimpan data: " . $conn->error;
}
$query->close();
$conn->close();
?>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+67
View File
@@ -0,0 +1,67 @@
<?php
session_start();
// Tolak akses jika bukan admin
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
http_response_code(403); // Status Forbidden
echo "Akses ditolak! Anda bukan operator.";
exit;
}
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = $_POST['id'];
$tabel = $_POST['tabel'];
$stmt = null;
if ($tabel == 'spbu_locations') {
$nama = $_POST['nama_spbu'];
$hp = $_POST['no_hp'];
$kategori = $_POST['kategori'];
$lat = $_POST['latitude'];
$lng = $_POST['longitude'];
$stmt = $conn->prepare("UPDATE spbu_locations SET nama_spbu=?, no_hp=?, kategori=?, latitude=?, longitude=? WHERE id=?");
$stmt->bind_param("sssddi", $nama, $hp, $kategori, $lat, $lng, $id);
} else if ($tabel == 'jalan') {
$nama = $_POST['nama_jalan']; $status = $_POST['status'];
$stmt = $conn->prepare("UPDATE jalan SET nama_jalan=?, status=? WHERE id=?");
$stmt->bind_param("ssi", $nama, $status, $id);
} else if ($tabel == 'parsil') {
$nama = $_POST['nama_pemilik']; $status = $_POST['status'];
$stmt = $conn->prepare("UPDATE parsil SET nama_pemilik=?, status=? WHERE id=?");
$stmt->bind_param("ssi", $nama, $status, $id);
} else if ($tabel == 'rumah_ibadah') {
$nama = $_POST['nama'];
$lat = $_POST['latitude'];
$lng = $_POST['longitude'];
$stmt = $conn->prepare("UPDATE rumah_ibadah SET nama=?, lat=?, lng=? WHERE id=?");
$stmt->bind_param("sddi", $nama, $lat, $lng, $id);
} else if ($tabel == 'penduduk_miskin') {
$nama = $_POST['nama'];
$alamat = $_POST['alamat'];
$lat = $_POST['latitude'];
$lng = $_POST['longitude'];
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_kk=?, alamat=?, lat=?, lng=? WHERE id=?");
$stmt->bind_param("ssddi", $nama, $alamat, $lat, $lng, $id);
}
if ($stmt) {
if ($stmt->execute()) {
echo "Data berhasil diperbarui!";
} else {
echo "Gagal memperbarui: " . $conn->error;
}
$stmt->close();
} else {
echo "Tabel tidak valid!";
}
$conn->close();
}
?>
+70
View File
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Landing Page - Tugas WebGIS</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f4f4f9;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
color: #333;
}
.link-list {
list-style-type: none;
padding: 0;
}
.link-list li {
margin: 15px 0;
}
.link-list a {
display: block;
padding: 10px 15px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 5px;
text-align: center;
font-weight: bold;
}
.link-list a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h2>Laman Tugas WebGIS</h2>
<p style="text-align: center;">Nama: Aleksander William | NIM: D1041231003</p>
<ul class="link-list">
<li><a href="./pertemuan1/index.php">Tugas Pertemuan 1</a></li>
<li><a href="./pertemuan2/index.php">Tugas Pertemuan 2</a></li>
<li><a href="./WebGISPovertyMap/index.php">Tugas Poverty Map</a></li>
</ul>
</div>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$tabel = isset($_POST['tabel']) ? trim($_POST['tabel']) : '';
if ($id <= 0) {
http_response_code(400);
echo "ID tidak valid!";
exit;
}
// Allowed tables for security
$allowed_tables = ['jalan', 'parsil'];
if (in_array($tabel, $allowed_tables)) {
$stmt = $conn->prepare("DELETE FROM $tabel WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo "Data berhasil dihapus!";
} else {
http_response_code(500);
echo "Gagal menghapus: " . $conn->error;
}
$stmt->close();
} else {
http_response_code(400);
echo "Tabel tidak valid!";
}
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+283
View File
@@ -0,0 +1,283 @@
<?php
// Start session for standard PHP structure (if needed in future)
session_start();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS Pertemuan 1 — Pemetaan Jalan & Parsil</title>
<!-- Leaflet & Leaflet.draw CDN -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<!-- Premium Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- Custom CSS Styles -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- ==============================
HEADER BAR
============================== -->
<div class="webgis-header">
<div class="header-brand">
<div class="brand-icon">
<!-- Map icon SVG -->
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
</div>
<div>
<div class="brand-name">WebGIS Spasial</div>
<div class="brand-subtitle">Pertemuan 1</div>
</div>
</div>
<div class="header-divider"></div>
<!-- SEARCH BAR -->
<div class="search-wrapper">
<div class="search-bar">
<!-- Category Dropdown -->
<div class="search-category-wrap" id="custom-select-wrap">
<span class="search-category-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 10h16M4 14h8" />
</svg>
</span>
<div class="custom-select-trigger" id="custom-select-trigger">Semua Layer</div>
<input type="hidden" id="search-category" value="Semua">
<span class="category-arrow">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</span>
<div class="custom-select-options" id="custom-select-options">
<div class="custom-option selected" data-value="Semua">Semua Layer</div>
<div class="custom-option" data-value="Jalan">Jaringan Jalan</div>
<div class="custom-option" data-value="Parsil">Parsil Lahan</div>
</div>
</div>
<!-- Input Pencarian -->
<div class="search-input-wrap">
<span class="search-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</span>
<input type="text" id="search-input" placeholder="Cari jalan atau pemilik parsil..."
onkeyup="jalankanPencarian()" oninput="toggleClearBtn()" autocomplete="off">
<button class="search-clear-btn" id="search-clear" onclick="clearSearch()" title="Hapus pencarian">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Dropdown Hasil Pencarian -->
<div class="search-results" id="search-results"></div>
</div>
<!-- HEADER RIGHT TOOLS -->
<div class="header-tools">
<div class="tool-badge">Peta Aktif</div>
<div class="tool-btn" title="Status Database">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
<div class="status-dot"></div>
</div>
</div>
</div>
<!-- ==============================
SIDEBAR INPUT DATA
============================== -->
<!-- Toggle Button -->
<button class="sidebar-toggle" id="sidebar-toggle" title="Panel Input Data">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
</svg>
<span>Input Geometris</span>
</button>
<!-- Sidebar Panel -->
<div class="input-sidebar" id="input-sidebar">
<div class="sidebar-header">
<div class="sidebar-title">
<div class="sidebar-title-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div class="sidebar-title-text">Panel Input Data</div>
<div class="sidebar-title-sub">Tambah polygon & polyline</div>
</div>
</div>
<button class="sidebar-close" id="sidebar-close" title="Tutup">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Tab Navigation -->
<div class="sidebar-tabs">
<button class="stab active" data-tab="jalan" onclick="switchSidebarTab('jalan')">
<span class="stab-icon">🛣️</span>
<span>Jalan (Polyline)</span>
</button>
<button class="stab" data-tab="parsil" onclick="switchSidebarTab('parsil')">
<span class="stab-icon">🏘️</span>
<span>Parsil (Polygon)</span>
</button>
</div>
<!-- Scrollable Content -->
<div class="sidebar-body">
<!-- ===== TAB: JALAN (POLYLINE) ===== -->
<div class="stab-content active" id="tab-jalan">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Gambar Ruas Jalan</strong> di bawah, lalu buat garis di peta. Klik ganda titik akhir untuk menyelesaikan gambar.</p>
</div>
<button class="btn-pick-location btn-draw" id="btn-draw-jalan" onclick="activateDrawMode('polyline')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
Gambar Ruas Jalan
</button>
<div class="coords-display" id="coords-jalan">
<span class="coords-label">Panjang Terhitung:</span>
<span class="coords-value" id="coords-jalan-val">Belum digambar</span>
</div>
<div class="step-guide">
<div class="step-badge">Langkah 2</div>
<p>Lengkapi atribut data jalan di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama Jalan <span class="required">*</span></label>
<input type="text" id="sb_nama_jalan" class="sinput" placeholder="Contoh: Jl. Ahmad Yani">
</div>
<div class="sfield">
<label class="sfield-label">Status Administrasi</label>
<select id="sb_status_jalan" class="sselect">
<option value="Jalan Nasional">Jalan Nasional</option>
<option value="Jalan Provinsi">Jalan Provinsi</option>
<option value="Jalan Kabupaten">Jalan Kabupaten</option>
</select>
</div>
<div class="sfield">
<label class="sfield-label">Panjang Terukur</label>
<input type="text" id="sb_panjang_jalan" class="sinput" placeholder="Otomatis terhitung setelah menggambar" readonly>
</div>
<button class="btn-save-sidebar" onclick="saveJalanSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Data Jalan
</button>
</div>
<!-- ===== TAB: PARSIL (POLYGON) ===== -->
<div class="stab-content" id="tab-parsil">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Gambar Area Parsil</strong> di bawah, lalu tentukan batas area di peta. Klik ganda titik terakhir untuk menyelesaikan.</p>
</div>
<button class="btn-pick-location" id="btn-draw-parsil" onclick="activateDrawMode('polygon')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
Gambar Area Parsil
</button>
<div class="coords-display" id="coords-parsil">
<span class="coords-label">Luas Terhitung:</span>
<span class="coords-value" id="coords-parsil-val">Belum digambar</span>
</div>
<div class="step-guide">
<div class="step-badge">Langkah 2</div>
<p>Lengkapi atribut parsil di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama Pemilik <span class="required">*</span></label>
<input type="text" id="sb_nama_parsil" class="sinput" placeholder="Contoh: Haji Sudirman">
</div>
<div class="sfield">
<label class="sfield-label">Status Kepemilikan (Sertifikat)</label>
<select id="sb_status_parsil" class="sselect">
<option value="SHM">SHM (Sertifikat Hak Milik)</option>
<option value="HGB">HGB (Hak Guna Bangunan)</option>
<option value="HGU">HGU (Hak Guna Usaha)</option>
<option value="HP">HP (Hak Pakai)</option>
</select>
</div>
<div class="sfield">
<label class="sfield-label">Luas Terukur</label>
<input type="text" id="sb_luas_parsil" class="sinput" placeholder="Otomatis terhitung setelah menggambar" readonly>
</div>
<button class="btn-save-sidebar" onclick="saveParsilSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Data Parsil
</button>
</div>
</div><!-- end sidebar-body -->
</div><!-- end input-sidebar -->
<!-- Banner Info Mode Menggambar -->
<div class="pick-mode-overlay" id="pick-overlay">
<div class="pick-mode-banner">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5" />
</svg>
<span id="pick-overlay-text">Klik lokasi di peta...</span>
<button onclick="deactivatePickMode()">Batal</button>
</div>
</div>
<!-- Container Peta -->
<div id="map" style="padding-top: 60px; height: 100vh; box-sizing: border-box;"></div>
<!-- Scripts Leaflet, Leaflet.draw, Turf, dan script.js -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script src="script.js"></script>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
<?php
$host = "localhost";
$user = "root";
$pass = "";
$db = "webgis_db_pertemuan1";
// Connect first without DB to ensure we can create it if missing
$conn = new mysqli($host, $user, $pass);
if ($conn->connect_error) {
die("Koneksi gagal: " . $conn->connect_error);
}
// Create database if not exists
$conn->query("CREATE DATABASE IF NOT EXISTS `$db` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");
// Select database
$conn->select_db($db);
// Auto create tables if not exist
// Table: jalan (for polyline data)
$sql_jalan = "CREATE TABLE IF NOT EXISTS `jalan` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`nama_jalan` VARCHAR(255) NOT NULL,
`status` VARCHAR(100) NOT NULL,
`panjang` DECIMAL(10,2) NOT NULL,
`koordinat` TEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
// Table: parsil (for polygon data)
$sql_parsil = "CREATE TABLE IF NOT EXISTS `parsil` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`nama_pemilik` VARCHAR(255) NOT NULL,
`status` VARCHAR(100) NOT NULL,
`luas` DECIMAL(10,2) NOT NULL,
`koordinat` TEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
if (!$conn->query($sql_jalan)) {
die("Gagal membuat tabel jalan: " . $conn->error);
}
if (!$conn->query($sql_parsil)) {
die("Gagal membuat tabel parsil: " . $conn->error);
}
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
include 'koneksi.php';
$jenis = isset($_GET['jenis']) ? $_GET['jenis'] : 'jalan';
if ($jenis == 'jalan') {
$sql = "SELECT * FROM jalan";
} else if ($jenis == 'parsil') {
$sql = "SELECT * FROM parsil";
} else {
die(json_encode(["error" => "Jenis data tidak ditemukan"]));
}
$result = $conn->query($sql);
$data = array();
if ($result && $result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
// Convert numbers correctly
if (isset($row['panjang'])) {
$row['panjang'] = floatval($row['panjang']);
}
if (isset($row['luas'])) {
$row['luas'] = floatval($row['luas']);
}
$data[] = $row;
}
}
header('Content-Type: application/json');
echo json_encode($data);
$conn->close();
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = isset($_POST['nama_jalan']) ? trim($_POST['nama_jalan']) : '';
$status = isset($_POST['status']) ? trim($_POST['status']) : '';
$panjang_raw = isset($_POST['panjang']) ? $_POST['panjang'] : '0';
$koordinat = isset($_POST['koordinat']) ? $_POST['koordinat'] : '';
if (empty($nama) || empty($koordinat)) {
http_response_code(400);
echo "Nama jalan dan koordinat wajib diisi!";
exit;
}
// Clean up " m" from length to get float value
$panjang = floatval(preg_replace('/[^\d.]/', '', $panjang_raw));
$stmt = $conn->prepare("INSERT INTO jalan (nama_jalan, status, panjang, koordinat) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssds", $nama, $status, $panjang, $koordinat);
if ($stmt->execute()) {
echo "Data Jalan berhasil disimpan!";
} else {
http_response_code(500);
echo "Gagal menyimpan data: " . $conn->error;
}
$stmt->close();
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = isset($_POST['nama_pemilik']) ? trim($_POST['nama_pemilik']) : '';
$status = isset($_POST['status']) ? trim($_POST['status']) : '';
$luas_raw = isset($_POST['luas']) ? $_POST['luas'] : '0';
$koordinat = isset($_POST['koordinat']) ? $_POST['koordinat'] : '';
if (empty($nama) || empty($koordinat)) {
http_response_code(400);
echo "Nama pemilik dan koordinat wajib diisi!";
exit;
}
// Clean up " m²" or other characters to get float value
$luas = floatval(preg_replace('/[^\d.]/', '', $luas_raw));
$stmt = $conn->prepare("INSERT INTO parsil (nama_pemilik, status, luas, koordinat) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssds", $nama, $status, $luas, $koordinat);
if ($stmt->execute()) {
echo "Data Parsil berhasil disimpan!";
} else {
http_response_code(500);
echo "Gagal menyimpan data: " . $conn->error;
}
$stmt->close();
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+577
View File
@@ -0,0 +1,577 @@
// ==========================================
// 1. INISIALISASI PETA & LAYER
// ==========================================
var map = L.map('map', { zoomControl: false }).setView([-0.0227, 109.3425], 14);
// Zoom control di kanan bawah (tampilan modern)
L.control.zoom({ position: 'bottomright' }).addTo(map);
// OpenStreetMap Tile Layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Layer Groups
var jalanLayer = L.layerGroup().addTo(map);
var parsilLayer = L.layerGroup().addTo(map);
var drawnItems = new L.FeatureGroup().addTo(map);
// Layer Control (Checkbox di kanan atas)
var overlayMaps = {
"🛣️ Jaringan Jalan": jalanLayer,
"🏘️ Parsil Bangunan": parsilLayer
};
L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map);
// Global Variables
var databasePencarian = [];
var sidebarDrawMode = null; // 'polyline' | 'polygon'
var sidebarKoordinat = "";
var sidebarDrawHandler = null;
// ==========================================
// 2. FUNGSI LOAD DATA DARI DATABASE
// ==========================================
function loadJalan() {
jalanLayer.clearLayers();
// Reset database pencarian khusus kategori Jalan
databasePencarian = databasePencarian.filter(item => item.kategori !== 'Jalan');
fetch('load.php?jenis=jalan')
.then(res => res.json())
.then(data => {
data.forEach(item => {
try {
var geom = JSON.parse(item.koordinat);
// Warna berdasarkan status jalan
var warna = item.status === 'Jalan Nasional' ? '#ef4444' // Red
: item.status === 'Jalan Provinsi' ? '#fbbf24' // Yellow/Amber
: '#10b981'; // Green (Kabupaten)
var viewHTML = `
<div class="popup-container" id="popup-jalan-${item.id}">
<div class="popup-header popup-header-jalan">
<h4>${item.nama_jalan}</h4>
<span class="popup-type-badge">🛣️ Jaringan Jalan</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Status Jalan</span>
<span class="info-value" style="color:${warna}; font-weight:600;">${item.status}</span>
</div>
<div class="info-row">
<span class="info-label">Panjang Terukur</span>
<span class="info-value">${item.panjang} m</span>
</div>
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditJalan('${item.id}', '${item.nama_jalan}', '${item.status}')">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('jalan', '${item.id}')">🗑️ Hapus</button>
</div>
</div>
</div>`;
var layerJalan = L.geoJSON(geom, {
style: { color: warna, weight: 5, opacity: 0.85 }
}).bindPopup(viewHTML).addTo(jalanLayer);
layerJalan.on('click', function () {
map.fitBounds(layerJalan.getBounds(), { padding: [50, 50] });
});
databasePencarian.push({
namaLower: item.nama_jalan.toLowerCase(),
namaAsli: item.nama_jalan,
kategori: 'Jalan',
ikon: '🛣️',
tipe: 'area',
layer: layerJalan
});
} catch (error) {
console.error("Error parse koordinat jalan ID: " + item.id, error);
}
});
})
.catch(e => console.log('Belum ada data Jalan', e));
}
function loadParsil() {
parsilLayer.clearLayers();
// Reset database pencarian khusus kategori Parsil
databasePencarian = databasePencarian.filter(item => item.kategori !== 'Parsil');
fetch('load.php?jenis=parsil')
.then(res => res.json())
.then(data => {
data.forEach(item => {
try {
var geom = JSON.parse(item.koordinat);
// Warna berdasarkan status sertifikat tanah
var warna = item.status === 'SHM' ? '#10b981' // Green
: item.status === 'HGB' ? '#3b82f6' // Blue
: item.status === 'HGU' ? '#8b5cf6' // Purple
: '#f59e0b'; // Orange (HP)
var viewHTML = `
<div class="popup-container" id="popup-parsil-${item.id}">
<div class="popup-header popup-header-parsil">
<h4>${item.nama_pemilik}</h4>
<span class="popup-type-badge">🏘️ Parsil Bangunan</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Status Sertifikat</span>
<span class="info-value" style="color:${warna}; font-weight:600;">${item.status}</span>
</div>
<div class="info-row">
<span class="info-label">Luas Terukur</span>
<span class="info-value">${item.luas} m²</span>
</div>
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditParsil('${item.id}', '${item.nama_pemilik}', '${item.status}')">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('parsil', '${item.id}')">🗑️ Hapus</button>
</div>
</div>
</div>`;
var layerParsil = L.geoJSON(geom, {
style: { fillColor: warna, color: warna, weight: 2, fillOpacity: 0.45 }
}).bindPopup(viewHTML).addTo(parsilLayer);
layerParsil.on('click', function () {
map.fitBounds(layerParsil.getBounds(), { padding: [50, 50] });
});
databasePencarian.push({
namaLower: item.nama_pemilik.toLowerCase(),
namaAsli: item.nama_pemilik,
kategori: 'Parsil',
ikon: '🏘️',
tipe: 'area',
layer: layerParsil
});
} catch (error) {
console.error("Error parse koordinat parsil ID: " + item.id, error);
}
});
})
.catch(e => console.log('Belum ada data Parsil', e));
}
// Load data awal
loadJalan();
loadParsil();
// ==========================================
// 3. EDIT & UPDATE DATA
// ==========================================
function formEditJalan(id, nama, status) {
setTimeout(function () {
var container = document.getElementById(`popup-jalan-${id}`);
if (!container) return;
container.innerHTML = `
<div class="popup-header popup-header-jalan" style="border-radius: 0;">
<h4 style="margin:0;">Edit Detail Jalan</h4>
</div>
<div class="popup-body">
<form onsubmit="updateData(event, 'jalan', '${id}')">
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Nama Jalan</span>
<input type="text" id="edit_nama" value="${nama}" required style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:12px;">
<span class="info-label">Status Jalan</span>
<select id="edit_status" style="margin-bottom:0;">
<option value="Jalan Nasional" ${status === 'Jalan Nasional' ? 'selected' : ''}>Jalan Nasional</option>
<option value="Jalan Provinsi" ${status === 'Jalan Provinsi' ? 'selected' : ''}>Jalan Provinsi</option>
<option value="Jalan Kabupaten" ${status === 'Jalan Kabupaten' ? 'selected' : ''}>Jalan Kabupaten</option>
</select>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Simpan</button>
<button type="button" class="btn btn-delete" onclick="loadJalan();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function formEditParsil(id, nama, status) {
setTimeout(function () {
var container = document.getElementById(`popup-parsil-${id}`);
if (!container) return;
container.innerHTML = `
<div class="popup-header popup-header-parsil" style="border-radius: 0;">
<h4 style="margin:0;">Edit Detail Parsil</h4>
</div>
<div class="popup-body">
<form onsubmit="updateData(event, 'parsil', '${id}')">
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Nama Pemilik</span>
<input type="text" id="edit_nama" value="${nama}" required style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:12px;">
<span class="info-label">Status Sertifikat</span>
<select id="edit_status" style="margin-bottom:0;">
<option value="SHM" ${status === 'SHM' ? 'selected' : ''}>SHM (Hak Milik)</option>
<option value="HGB" ${status === 'HGB' ? 'selected' : ''}>HGB (Guna Bangunan)</option>
<option value="HGU" ${status === 'HGU' ? 'selected' : ''}>HGU (Guna Usaha)</option>
<option value="HP" ${status === 'HP' ? 'selected' : ''}>HP (Hak Pakai)</option>
</select>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Simpan</button>
<button type="button" class="btn btn-delete" onclick="loadParsil();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function updateData(e, tabel, id) {
e.preventDefault();
var formData = new FormData();
formData.append('tabel', tabel);
formData.append('id', id);
if (tabel === 'jalan') {
formData.append('nama_jalan', document.getElementById('edit_nama').value);
formData.append('status', document.getElementById('edit_status').value);
} else if (tabel === 'parsil') {
formData.append('nama_pemilik', document.getElementById('edit_nama').value);
formData.append('status', document.getElementById('edit_status').value);
}
fetch('update_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
if (tabel === 'jalan') loadJalan();
if (tabel === 'parsil') loadParsil();
})
.catch(error => console.error("Error updating data:", error));
}
// ==========================================
// 4. HAPUS DATA
// ==========================================
function deleteData(tabel, id) {
if (confirm('Apakah Anda yakin ingin menghapus data ini secara permanen?')) {
var formData = new FormData();
formData.append('id', id);
formData.append('tabel', tabel);
fetch('delete_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
if (tabel === 'jalan') loadJalan();
if (tabel === 'parsil') loadParsil();
})
.catch(error => console.error("Error deleting data:", error));
}
}
// ==========================================
// 5. SISTEM PENCARIAN & AUTOCMPLETE
// ==========================================
function jalankanPencarian() {
var kataKunci = document.getElementById('search-input').value.toLowerCase();
var kategori = document.getElementById('search-category').value;
var wadahHasil = document.getElementById('search-results');
wadahHasil.innerHTML = '';
if (kataKunci.length < 2) {
wadahHasil.style.display = 'none';
return;
}
var hasil = databasePencarian.filter(function (item) {
var cocokNama = item.namaLower.includes(kataKunci);
var cocokKategori = (kategori === 'Semua') || (item.kategori === kategori);
return cocokNama && cocokKategori;
});
if (hasil.length > 0) {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `<div class="results-header">${hasil.length} hasil ditemukan</div>`;
hasil.forEach(function (item) {
var badgeClass = item.kategori === 'Jalan' ? 'badge-Jalan' : 'badge-Parsil';
var bgColors = item.kategori === 'Jalan' ? 'rgba(245,158,11,0.1)' : 'rgba(163,116,255,0.1)';
var div = document.createElement('div');
div.className = 'result-item';
div.innerHTML = `
<div class="result-item-icon" style="background: ${bgColors};">
${item.ikon}
</div>
<div class="result-item-text">
<div class="result-item-name">${item.namaAsli}</div>
<div class="result-item-sub">Klik untuk zoom ke lokasi</div>
</div>
<span class="badge-kategori ${badgeClass}">${item.kategori}</span>`;
div.onclick = function () {
map.fitBounds(item.layer.getBounds(), { padding: [50, 50] });
item.layer.openPopup();
wadahHasil.style.display = 'none';
document.getElementById('search-input').value = item.namaAsli;
toggleClearBtn();
};
wadahHasil.appendChild(div);
});
} else {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `
<div class="result-empty">
<div class="result-item" style="color:#ef4444; justify-content:center;">Data tidak ditemukan</div>
</div>`;
}
}
function toggleClearBtn() {
var input = document.getElementById('search-input');
var clearBtn = document.getElementById('search-clear');
clearBtn.style.display = input.value.length > 0 ? 'flex' : 'none';
}
function clearSearch() {
document.getElementById('search-input').value = '';
document.getElementById('search-results').style.display = 'none';
document.getElementById('search-clear').style.display = 'none';
}
// Tutup hasil saat klik di luar
document.addEventListener('click', function (e) {
var wrapper = document.querySelector('.search-wrapper');
if (wrapper && !wrapper.contains(e.target)) {
document.getElementById('search-results').style.display = 'none';
}
});
// Shortcut Ctrl + K untuk fokus ke pencarian
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('search-input').focus();
}
if (e.key === 'Escape') {
clearSearch();
document.getElementById('search-input').blur();
}
});
// ==========================================
// 6. LOGIKA INPUT SIDEBAR & PROGRAMMATIC DRAW
// ==========================================
// Buka/Tutup Sidebar
document.getElementById('sidebar-toggle').addEventListener('click', function () {
var sidebar = document.getElementById('input-sidebar');
sidebar.classList.toggle('open');
this.classList.toggle('open');
});
document.getElementById('sidebar-close').addEventListener('click', function () {
document.getElementById('input-sidebar').classList.remove('open');
document.getElementById('sidebar-toggle').classList.remove('open');
});
// Ganti Tab
function switchSidebarTab(tabName) {
document.querySelectorAll('.stab').forEach(function (t) { t.classList.remove('active'); });
document.querySelectorAll('.stab-content').forEach(function (c) { c.classList.remove('active'); });
document.querySelector('.stab[data-tab="' + tabName + '"]').classList.add('active');
document.getElementById('tab-' + tabName).classList.add('active');
// Batalkan mode draw jika ganti tab
deactivatePickMode();
}
// Aktifkan mode gambar garis/poligon secara terprogram
function activateDrawMode(type) {
deactivatePickMode();
sidebarDrawMode = type;
if (type === 'polyline') {
sidebarDrawHandler = new L.Draw.Polyline(map, {
shapeOptions: { color: '#6366f1', weight: 4 }
});
document.getElementById('coords-jalan-val').textContent = 'Sedang menggambar...';
document.getElementById('btn-draw-jalan').classList.add('active-pick');
} else if (type === 'polygon') {
sidebarDrawHandler = new L.Draw.Polygon(map, {
shapeOptions: { color: '#6366f1', fillColor: '#6366f1', fillOpacity: 0.3 }
});
document.getElementById('coords-parsil-val').textContent = 'Sedang menggambar...';
document.getElementById('btn-draw-parsil').classList.add('active-pick');
}
if (sidebarDrawHandler) sidebarDrawHandler.enable();
document.getElementById('pick-overlay').classList.add('active');
var teks = {
polyline: 'Klik titik-titik ruas jalan di peta. Klik dua kali titik terakhir untuk selesai.',
polygon: 'Klik titik-titik batas lahan di peta. Klik dua kali titik terakhir untuk selesai.'
};
document.getElementById('pick-overlay-text').textContent = teks[type];
map.getContainer().style.cursor = 'crosshair';
}
// Batalkan mode draw
function deactivatePickMode() {
sidebarDrawMode = null;
document.getElementById('pick-overlay').classList.remove('active');
document.querySelectorAll('.btn-pick-location').forEach(function (b) { b.classList.remove('active-pick'); });
map.getContainer().style.cursor = '';
if (sidebarDrawHandler) {
try { sidebarDrawHandler.disable(); } catch (e) { }
sidebarDrawHandler = null;
}
}
// Menangkap event Leaflet Draw CREATED
map.on(L.Draw.Event.CREATED, function (e) {
var type = e.layerType, layer = e.layer;
var geojson = layer.toGeoJSON();
drawnItems.clearLayers();
drawnItems.addLayer(layer);
if (sidebarDrawMode === 'polyline' && type === 'polyline') {
// turf.length menghitung dalam KM, ubah ke meter
var panjangMeter = (turf.length(geojson, { units: 'kilometers' }) * 1000).toFixed(2);
sidebarKoordinat = JSON.stringify(geojson.geometry);
var el = document.getElementById('coords-jalan-val');
el.textContent = panjangMeter + ' m';
el.classList.add('has-coords');
document.getElementById('sb_panjang_jalan').value = panjangMeter + ' m';
deactivatePickMode();
} else if (sidebarDrawMode === 'polygon' && type === 'polygon') {
// turf.area menghitung dalam meter persegi
var luasMeter = turf.area(geojson).toFixed(2);
sidebarKoordinat = JSON.stringify(geojson.geometry);
var el = document.getElementById('coords-parsil-val');
el.textContent = luasMeter + ' m²';
el.classList.add('has-coords');
document.getElementById('sb_luas_parsil').value = luasMeter + ' m²';
deactivatePickMode();
}
});
// ==========================================
// 7. SIMPAN DATA DARI SIDEBAR
// ==========================================
function saveJalanSidebar() {
if (!sidebarKoordinat) {
alert('Silakan gambar ruas jalan di peta terlebih dahulu!');
return;
}
var nama = document.getElementById('sb_nama_jalan').value.trim();
if (!nama) {
alert('Nama jalan wajib diisi!');
return;
}
var formData = new FormData();
formData.append('nama_jalan', nama);
formData.append('status', document.getElementById('sb_status_jalan').value);
formData.append('panjang', document.getElementById('sb_panjang_jalan').value);
formData.append('koordinat', sidebarKoordinat);
fetch('save_jalan.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
document.getElementById('sb_nama_jalan').value = '';
document.getElementById('sb_panjang_jalan').value = '';
document.getElementById('coords-jalan-val').textContent = 'Belum digambar';
document.getElementById('coords-jalan-val').classList.remove('has-coords');
sidebarKoordinat = '';
drawnItems.clearLayers();
loadJalan();
})
.catch(e => console.error("Error saving jalan:", e));
}
function saveParsilSidebar() {
if (!sidebarKoordinat) {
alert('Silakan gambar area parsil di peta terlebih dahulu!');
return;
}
var nama = document.getElementById('sb_nama_parsil').value.trim();
if (!nama) {
alert('Nama pemilik wajib diisi!');
return;
}
var formData = new FormData();
formData.append('nama_pemilik', nama);
formData.append('status', document.getElementById('sb_status_parsil').value);
formData.append('luas', document.getElementById('sb_luas_parsil').value);
formData.append('koordinat', sidebarKoordinat);
fetch('save_parsil.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
document.getElementById('sb_nama_parsil').value = '';
document.getElementById('sb_luas_parsil').value = '';
document.getElementById('coords-parsil-val').textContent = 'Belum digambar';
document.getElementById('coords-parsil-val').classList.remove('has-coords');
sidebarKoordinat = '';
drawnItems.clearLayers();
loadParsil();
})
.catch(e => console.error("Error saving parsil:", e));
}
// ==========================================
// 8. DROPDOWN KATEGORI DI HEADER
// ==========================================
document.addEventListener('DOMContentLoaded', function () {
var wrap = document.getElementById('custom-select-wrap');
var trigger = document.getElementById('custom-select-trigger');
var options = document.getElementById('custom-select-options');
var hiddenInput = document.getElementById('search-category');
var optionItems = document.querySelectorAll('.custom-option');
// Toggle dropdown
wrap.addEventListener('click', function (e) {
e.stopPropagation();
options.classList.toggle('show');
});
// Pilihan dropdown diklik
optionItems.forEach(function (item) {
item.addEventListener('click', function (e) {
e.stopPropagation();
optionItems.forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
trigger.textContent = this.textContent;
hiddenInput.value = this.getAttribute('data-value');
options.classList.remove('show');
jalankanPencarian();
});
});
// Tutup dropdown jika klik di luar
document.addEventListener('click', function () {
options.classList.remove('show');
});
});
+1240
View File
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$tabel = isset($_POST['tabel']) ? trim($_POST['tabel']) : '';
$stmt = null;
if ($id <= 0) {
http_response_code(400);
echo "ID tidak valid!";
exit;
}
if ($tabel == 'jalan') {
$nama = isset($_POST['nama_jalan']) ? trim($_POST['nama_jalan']) : '';
$status = isset($_POST['status']) ? trim($_POST['status']) : '';
if (empty($nama)) {
http_response_code(400);
echo "Nama jalan tidak boleh kosong!";
exit;
}
$stmt = $conn->prepare("UPDATE jalan SET nama_jalan=?, status=? WHERE id=?");
$stmt->bind_param("ssi", $nama, $status, $id);
} else if ($tabel == 'parsil') {
$nama = isset($_POST['nama_pemilik']) ? trim($_POST['nama_pemilik']) : '';
$status = isset($_POST['status']) ? trim($_POST['status']) : '';
if (empty($nama)) {
http_response_code(400);
echo "Nama pemilik tidak boleh kosong!";
exit;
}
$stmt = $conn->prepare("UPDATE parsil SET nama_pemilik=?, status=? WHERE id=?");
$stmt->bind_param("ssi", $nama, $status, $id);
} else {
http_response_code(400);
echo "Tabel tidak valid!";
exit;
}
if ($stmt) {
if ($stmt->execute()) {
echo "Data berhasil diperbarui!";
} else {
http_response_code(500);
echo "Gagal memperbarui: " . $conn->error;
}
$stmt->close();
}
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$tabel = isset($_POST['tabel']) ? trim($_POST['tabel']) : '';
if ($id <= 0) {
http_response_code(400);
echo "ID tidak valid!";
exit;
}
$allowed_tables = ['spbu_locations'];
if (in_array($tabel, $allowed_tables)) {
$stmt = $conn->prepare("DELETE FROM $tabel WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo "Data berhasil dihapus!";
} else {
http_response_code(500);
echo "Gagal menghapus: " . $conn->error;
}
$stmt->close();
} else {
http_response_code(400);
echo "Tabel tidak valid!";
}
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+225
View File
@@ -0,0 +1,225 @@
<?php
// Start session for standard PHP structure (if needed in future)
session_start();
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS Pertemuan 2 — Pemetaan Stasiun SPBU</title>
<!-- Leaflet & Leaflet.draw CDN -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<!-- Premium Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- Custom CSS Styles -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- ==============================
HEADER BAR
============================== -->
<div class="webgis-header">
<div class="header-brand">
<div class="brand-icon">
<!-- Map icon SVG -->
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" />
</svg>
</div>
<div>
<div class="brand-name">WebGIS SPBU</div>
<div class="brand-subtitle">Pertemuan 2</div>
</div>
</div>
<div class="header-divider"></div>
<!-- SEARCH BAR -->
<div class="search-wrapper">
<div class="search-bar">
<!-- Category Dropdown -->
<div class="search-category-wrap" id="custom-select-wrap">
<span class="search-category-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 10h16M4 14h8" />
</svg>
</span>
<div class="custom-select-trigger" id="custom-select-trigger">Semua Layer</div>
<input type="hidden" id="search-category" value="Semua">
<span class="category-arrow">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</span>
<div class="custom-select-options" id="custom-select-options">
<div class="custom-option selected" data-value="Semua">Semua Layer</div>
<div class="custom-option" data-value="SPBU">Stasiun SPBU</div>
</div>
</div>
<!-- Input Pencarian -->
<div class="search-input-wrap">
<span class="search-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</span>
<input type="text" id="search-input" placeholder="Cari nama stasiun SPBU..."
onkeyup="jalankanPencarian()" oninput="toggleClearBtn()" autocomplete="off">
<button class="search-clear-btn" id="search-clear" onclick="clearSearch()" title="Hapus pencarian">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Dropdown Hasil Pencarian -->
<div class="search-results" id="search-results"></div>
</div>
<!-- HEADER RIGHT TOOLS -->
<div class="header-tools">
<div class="tool-badge">Peta Aktif</div>
<div class="tool-btn" title="Status Database">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
<div class="status-dot"></div>
</div>
</div>
</div>
<!-- ==============================
SIDEBAR INPUT DATA
============================== -->
<!-- Toggle Button -->
<button class="sidebar-toggle" id="sidebar-toggle" title="Panel Input Data">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
</svg>
<span>Input Titik</span>
</button>
<!-- Sidebar Panel -->
<div class="input-sidebar" id="input-sidebar">
<div class="sidebar-header">
<div class="sidebar-title">
<div class="sidebar-title-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div class="sidebar-title-text">Panel Input Data</div>
<div class="sidebar-title-sub">Tambah penanda SPBU</div>
</div>
</div>
<button class="sidebar-close" id="sidebar-close" title="Tutup">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Tab Navigation -->
<div class="sidebar-tabs">
<div class="stab active">
<span class="stab-icon">⛽</span>
<span>SPBU (Marker)</span>
</div>
</div>
<!-- Scrollable Content -->
<div class="sidebar-body">
<!-- ===== TAB: SPBU ===== -->
<div class="stab-content active">
<div class="step-guide">
<div class="step-badge">Langkah 1</div>
<p>Klik tombol <strong>Tandai Lokasi SPBU</strong> di bawah, lalu klik di peta pada posisi SPBU yang diinginkan.</p>
</div>
<button class="btn-pick-location" id="btn-pick-spbu" onclick="activatePickMode()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Tandai Lokasi SPBU
</button>
<div class="coords-display" id="coords-spbu">
<span class="coords-label">Koordinat:</span>
<span class="coords-value" id="coords-spbu-val">Belum dipilih</span>
</div>
<div class="step-guide">
<div class="step-badge">Langkah 2</div>
<p>Lengkapi atribut data SPBU di bawah ini.</p>
</div>
<div class="sfield">
<label class="sfield-label">Nama SPBU <span class="required">*</span></label>
<input type="text" id="sb_nama_spbu" class="sinput" placeholder="Contoh: SPBU Ahmad Yani">
</div>
<div class="sfield">
<label class="sfield-label">Nomor Telepon</label>
<input type="text" id="sb_nohp_spbu" class="sinput" placeholder="Contoh: 0561-xxxxxx">
</div>
<div class="sfield">
<label class="sfield-label">Status Operasional</label>
<select id="sb_kategori_spbu" class="sselect">
<option value="Ya">Buka 24 Jam</option>
<option value="Tidak">Reguler (Jam Kerja)</option>
</select>
</div>
<button class="btn-save-sidebar" onclick="saveSPBUSidebar()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
Simpan Data SPBU
</button>
</div>
</div><!-- end sidebar-body -->
</div><!-- end input-sidebar -->
<!-- Banner Info Mode Memilih Lokasi -->
<div class="pick-mode-overlay" id="pick-overlay">
<div class="pick-mode-banner">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5" />
</svg>
<span id="pick-overlay-text">Klik lokasi di peta...</span>
<button onclick="deactivatePickMode()">Batal</button>
</div>
</div>
<!-- Container Peta -->
<div id="map" style="padding-top: 60px; height: 100vh; box-sizing: border-box;"></div>
<!-- Scripts Leaflet, Leaflet.draw, Turf, dan script.js -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script src="script.js"></script>
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
<?php
$host = "localhost";
$user = "root";
$pass = "";
$db = "webgis_db_pertemuan2";
// Connect first without DB to ensure we can create it if missing
$conn = new mysqli($host, $user, $pass);
if ($conn->connect_error) {
die("Koneksi gagal: " . $conn->connect_error);
}
// Create database if not exists
$conn->query("CREATE DATABASE IF NOT EXISTS `$db` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");
// Select database
$conn->select_db($db);
// Auto create table spbu_locations if not exists
$sql_spbu = "CREATE TABLE IF NOT EXISTS `spbu_locations` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`nama_spbu` VARCHAR(255) NOT NULL,
`no_hp` VARCHAR(50) DEFAULT NULL,
`kategori` VARCHAR(50) NOT NULL,
`latitude` DOUBLE NOT NULL,
`longitude` DOUBLE NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
if (!$conn->query($sql_spbu)) {
die("Gagal membuat tabel spbu_locations: " . $conn->error);
}
?>
+30
View File
@@ -0,0 +1,30 @@
<?php
include 'koneksi.php';
$jenis = isset($_GET['jenis']) ? $_GET['jenis'] : 'spbu';
if ($jenis == 'spbu') {
$sql = "SELECT * FROM spbu_locations";
} else {
die(json_encode(["error" => "Jenis data tidak ditemukan"]));
}
$result = $conn->query($sql);
$data = array();
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
if (isset($row['latitude'])) {
$row['latitude'] = floatval($row['latitude']);
}
if (isset($row['longitude'])) {
$row['longitude'] = floatval($row['longitude']);
}
$data[] = $row;
}
}
header('Content-Type: application/json');
echo json_encode($data);
$conn->close();
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$nama = isset($_POST['nama_spbu']) ? trim($_POST['nama_spbu']) : '';
$hp = isset($_POST['no_hp']) ? trim($_POST['no_hp']) : '';
$kategori = isset($_POST['kategori']) ? trim($_POST['kategori']) : '';
$lat = isset($_POST['latitude']) ? floatval($_POST['latitude']) : 0.0;
$lng = isset($_POST['longitude']) ? floatval($_POST['longitude']) : 0.0;
if (empty($nama) || $lat == 0.0 || $lng == 0.0) {
http_response_code(400);
echo "Nama SPBU dan koordinat lokasi wajib diisi!";
exit;
}
$stmt = $conn->prepare("INSERT INTO spbu_locations (nama_spbu, no_hp, kategori, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param("sssdd", $nama, $hp, $kategori, $lat, $lng);
if ($stmt->execute()) {
echo "Data SPBU berhasil disimpan!";
} else {
http_response_code(500);
echo "Gagal menyimpan data: " . $conn->error;
}
$stmt->close();
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>
+438
View File
@@ -0,0 +1,438 @@
// ==========================================
// 1. INISIALISASI PETA & LAYER
// ==========================================
var map = L.map('map', { zoomControl: false }).setView([-0.0227, 109.3425], 14);
// Zoom control di kanan bawah
L.control.zoom({ position: 'bottomright' }).addTo(map);
// OpenStreetMap Tile Layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Layer Groups
var spbu24JamLayer = L.layerGroup().addTo(map);
var spbuRegulerLayer = L.layerGroup().addTo(map);
var drawnItems = new L.FeatureGroup().addTo(map);
// Layer Control (Checkbox di kanan atas)
var overlayMaps = {
"⛽ SPBU Buka 24 Jam": spbu24JamLayer,
"⛽ SPBU Reguler": spbuRegulerLayer
};
L.control.layers(null, overlayMaps, { collapsed: true, position: 'bottomleft' }).addTo(map);
// Standard Leaflet Marker Icons
var greenIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
});
var redIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
});
// Global Variables
var databasePencarian = [];
var spbuMarkers = {};
var sidebarPickMode = false;
var sidebarTempLat = 0.0;
var sidebarTempLng = 0.0;
// ==========================================
// 2. FUNGSI LOAD DATA DARI DATABASE
// ==========================================
function loadSPBU() {
spbu24JamLayer.clearLayers();
spbuRegulerLayer.clearLayers();
spbuMarkers = {};
databasePencarian = [];
fetch('load.php?jenis=spbu')
.then(res => res.json())
.then(data => {
data.forEach(item => {
var is24Jam = item.kategori === 'Ya';
var statusBuka = is24Jam ? 'Buka 24 Jam' : 'Reguler';
var icon = is24Jam ? greenIcon : redIcon;
var statusColor = is24Jam ? '#10b981' : '#ef4444';
var viewHTML = `
<div class="popup-container" id="popup-spbu-${item.id}">
<div class="popup-header">
<h4>${item.nama_spbu}</h4>
<span class="popup-type-badge">⛽ Stasiun SPBU</span>
</div>
<div class="popup-body">
<div class="info-row">
<span class="info-label">Telepon</span>
<span class="info-value">${item.no_hp || '—'}</span>
</div>
<div class="info-row">
<span class="info-label">Status Operasional</span>
<span class="info-value" style="color:${statusColor}; font-weight:600;">● ${statusBuka}</span>
</div>
<div class="btn-group">
<button class="btn btn-edit" onclick="formEditSPBU('${item.id}', '${item.nama_spbu}', '${item.no_hp}', '${item.kategori}')">✏️ Edit</button>
<button class="btn btn-delete" onclick="deleteData('spbu_locations', '${item.id}')">🗑️ Hapus</button>
</div>
</div>
</div>`;
var marker = L.marker([item.latitude, item.longitude], { icon: icon, draggable: false });
if (is24Jam) {
marker.addTo(spbu24JamLayer);
} else {
marker.addTo(spbuRegulerLayer);
}
marker.bindPopup(viewHTML);
spbuMarkers[String(item.id)] = marker;
marker.on('click', function () {
map.flyTo(marker.getLatLng(), 16, { animate: true });
});
// Update coordinates input on marker drag end (during edit)
marker.on('dragend', function (e) {
var position = marker.getLatLng();
if (document.getElementById('edit_lat')) {
document.getElementById('edit_lat').value = position.lat.toFixed(6);
document.getElementById('edit_lng').value = position.lng.toFixed(6);
}
marker.openPopup();
});
databasePencarian.push({
namaLower: item.nama_spbu.toLowerCase(),
namaAsli: item.nama_spbu,
kategori: 'SPBU',
ikon: '⛽',
tipe: 'titik',
layer: marker
});
});
})
.catch(e => console.log('Belum ada data SPBU', e));
}
// Load data awal
loadSPBU();
// ==========================================
// 3. EDIT & UPDATE DATA
// ==========================================
function formEditSPBU(id, nama, hp, kategori) {
var markerId = String(id);
var currentMarker = spbuMarkers[markerId];
if (currentMarker) {
currentMarker.dragging.enable();
}
setTimeout(function () {
var container = document.getElementById(`popup-spbu-${id}`);
if (!container) return;
var lat = currentMarker.getLatLng().lat.toFixed(6);
var lng = currentMarker.getLatLng().lng.toFixed(6);
container.innerHTML = `
<div class="popup-header" style="border-radius: 0;">
<h4 style="margin:0;">Edit Lokasi SPBU</h4>
<span class="popup-type-badge">📍 Geser marker untuk mengubah posisi</span>
</div>
<div class="popup-body">
<form onsubmit="updateData(event, 'spbu_locations', '${id}')">
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Nama SPBU</span>
<input type="text" id="edit_nama" value="${nama}" required style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:8px;">
<span class="info-label">Telepon</span>
<input type="text" id="edit_hp" value="${hp}" style="margin-bottom:0;">
</div>
<div class="sfield" style="margin-bottom:12px;">
<span class="info-label">Status Operasional</span>
<select id="edit_kategori" style="margin-bottom:0;">
<option value="Ya" ${kategori === 'Ya' ? 'selected' : ''}>Buka 24 Jam</option>
<option value="Tidak" ${kategori === 'Tidak' ? 'selected' : ''}>Reguler</option>
</select>
</div>
<input type="hidden" id="edit_lat" value="${lat}">
<input type="hidden" id="edit_lng" value="${lng}">
<div class="btn-group">
<button type="submit" class="btn btn-save">💾 Simpan</button>
<button type="button" class="btn btn-delete" onclick="loadSPBU();">Batal</button>
</div>
</form>
</div>`;
}, 50);
}
function updateData(e, tabel, id) {
e.preventDefault();
var formData = new FormData();
formData.append('tabel', tabel);
formData.append('id', id);
formData.append('nama_spbu', document.getElementById('edit_nama').value);
formData.append('no_hp', document.getElementById('edit_hp').value);
formData.append('kategori', document.getElementById('edit_kategori').value);
// Get current dragged position
var markerPosisi = spbuMarkers[String(id)].getLatLng();
formData.append('latitude', markerPosisi.lat);
formData.append('longitude', markerPosisi.lng);
fetch('update_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
loadSPBU();
})
.catch(error => console.error("Error updating data:", error));
}
// ==========================================
// 4. HAPUS DATA
// ==========================================
function deleteData(tabel, id) {
if (confirm('Apakah Anda yakin ingin menghapus data SPBU ini?')) {
var formData = new FormData();
formData.append('id', id);
formData.append('tabel', tabel);
fetch('delete_data.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
map.closePopup();
loadSPBU();
})
.catch(error => console.error("Error deleting data:", error));
}
}
// ==========================================
// 5. SISTEM PENCARIAN & ZOOM
// ==========================================
function jalankanPencarian() {
var kataKunci = document.getElementById('search-input').value.toLowerCase();
var kategori = document.getElementById('search-category').value;
var wadahHasil = document.getElementById('search-results');
wadahHasil.innerHTML = '';
if (kataKunci.length < 2) {
wadahHasil.style.display = 'none';
return;
}
var hasil = databasePencarian.filter(function (item) {
var cocokNama = item.namaLower.includes(kataKunci);
var cocokKategori = (kategori === 'Semua') || (item.kategori === kategori);
return cocokNama && cocokKategori;
});
if (hasil.length > 0) {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `<div class="results-header">${hasil.length} hasil ditemukan</div>`;
hasil.forEach(function (item) {
var div = document.createElement('div');
div.className = 'result-item';
div.innerHTML = `
<div class="result-item-icon" style="background: rgba(16,185,129,0.1);">
${item.ikon}
</div>
<div class="result-item-text">
<div class="result-item-name">${item.namaAsli}</div>
<div class="result-item-sub">Klik untuk zoom ke lokasi</div>
</div>
<span class="badge-kategori badge-SPBU">${item.kategori}</span>`;
div.onclick = function () {
map.flyTo(item.layer.getLatLng(), 16, { animate: true, duration: 1.5 });
item.layer.openPopup();
wadahHasil.style.display = 'none';
document.getElementById('search-input').value = item.namaAsli;
toggleClearBtn();
};
wadahHasil.appendChild(div);
});
} else {
wadahHasil.style.display = 'block';
wadahHasil.innerHTML = `
<div class="result-empty">
<div class="result-item" style="color:#ef4444; justify-content:center;">Data tidak ditemukan</div>
</div>`;
}
}
function toggleClearBtn() {
var input = document.getElementById('search-input');
var clearBtn = document.getElementById('search-clear');
clearBtn.style.display = input.value.length > 0 ? 'flex' : 'none';
}
function clearSearch() {
document.getElementById('search-input').value = '';
document.getElementById('search-results').style.display = 'none';
document.getElementById('search-clear').style.display = 'none';
}
// Tutup hasil saat klik di luar
document.addEventListener('click', function (e) {
var wrapper = document.querySelector('.search-wrapper');
if (wrapper && !wrapper.contains(e.target)) {
document.getElementById('search-results').style.display = 'none';
}
});
// Shortcut Ctrl + K untuk fokus ke pencarian
document.addEventListener('keydown', function (e) {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('search-input').focus();
}
if (e.key === 'Escape') {
clearSearch();
document.getElementById('search-input').blur();
}
});
// ==========================================
// 6. LOGIKA INPUT SIDEBAR & PROGRAMMATIC PICK
// ==========================================
// Buka/Tutup Sidebar
document.getElementById('sidebar-toggle').addEventListener('click', function () {
var sidebar = document.getElementById('input-sidebar');
sidebar.classList.toggle('open');
this.classList.toggle('open');
});
document.getElementById('sidebar-close').addEventListener('click', function () {
document.getElementById('input-sidebar').classList.remove('open');
document.getElementById('sidebar-toggle').classList.remove('open');
});
// Aktifkan mode pilih titik di peta
function activatePickMode() {
sidebarPickMode = true;
document.getElementById('pick-overlay').classList.add('active');
document.getElementById('btn-pick-spbu').classList.add('active-pick');
map.getContainer().style.cursor = 'crosshair';
}
// Batalkan mode pick
function deactivatePickMode() {
sidebarPickMode = false;
document.getElementById('pick-overlay').classList.remove('active');
document.getElementById('btn-pick-spbu').classList.remove('active-pick');
map.getContainer().style.cursor = '';
}
// Tangkap klik peta untuk mendapatkan koordinat
map.on('click', function (e) {
if (!sidebarPickMode) return;
var lat = e.latlng.lat;
var lng = e.latlng.lng;
sidebarTempLat = lat;
sidebarTempLng = lng;
var el = document.getElementById('coords-spbu-val');
el.textContent = lat.toFixed(6) + ', ' + lng.toFixed(6);
el.classList.add('has-coords');
// Tempatkan penanda sementara
drawnItems.clearLayers();
L.marker([lat, lng], { icon: redIcon }).addTo(drawnItems);
deactivatePickMode();
});
// ==========================================
// 7. SIMPAN DATA DARI SIDEBAR
// ==========================================
function saveSPBUSidebar() {
if (!sidebarTempLat || !sidebarTempLng) {
alert('Silakan pilih lokasi SPBU di peta terlebih dahulu!');
return;
}
var nama = document.getElementById('sb_nama_spbu').value.trim();
if (!nama) {
alert('Nama SPBU wajib diisi!');
return;
}
var formData = new FormData();
formData.append('nama_spbu', nama);
formData.append('no_hp', document.getElementById('sb_nohp_spbu').value.trim());
formData.append('kategori', document.getElementById('sb_kategori_spbu').value);
formData.append('latitude', sidebarTempLat);
formData.append('longitude', sidebarTempLng);
fetch('save.php', { method: 'POST', body: formData })
.then(r => r.text())
.then(res => {
alert(res);
document.getElementById('sb_nama_spbu').value = '';
document.getElementById('sb_nohp_spbu').value = '';
document.getElementById('coords-spbu-val').textContent = 'Belum dipilih';
document.getElementById('coords-spbu-val').classList.remove('has-coords');
sidebarTempLat = 0.0;
sidebarTempLng = 0.0;
drawnItems.clearLayers();
loadSPBU();
})
.catch(e => console.error("Error saving SPBU:", e));
}
// ==========================================
// 8. DROPDOWN KATEGORI DI HEADER
// ==========================================
document.addEventListener('DOMContentLoaded', function () {
var wrap = document.getElementById('custom-select-wrap');
var trigger = document.getElementById('custom-select-trigger');
var options = document.getElementById('custom-select-options');
var hiddenInput = document.getElementById('search-category');
var optionItems = document.querySelectorAll('.custom-option');
// Toggle dropdown
wrap.addEventListener('click', function (e) {
e.stopPropagation();
options.classList.toggle('show');
});
// Pilihan dropdown diklik
optionItems.forEach(function (item) {
item.addEventListener('click', function (e) {
e.stopPropagation();
optionItems.forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
trigger.textContent = this.textContent;
hiddenInput.value = this.getAttribute('data-value');
options.classList.remove('show');
jalankanPencarian();
});
});
// Tutup dropdown jika klik di luar
document.addEventListener('click', function () {
options.classList.remove('show');
});
});
+1201
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
<?php
include 'koneksi.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
$tabel = isset($_POST['tabel']) ? trim($_POST['tabel']) : '';
if ($id <= 0) {
http_response_code(400);
echo "ID tidak valid!";
exit;
}
if ($tabel === 'spbu_locations') {
$nama = isset($_POST['nama_spbu']) ? trim($_POST['nama_spbu']) : '';
$hp = isset($_POST['no_hp']) ? trim($_POST['no_hp']) : '';
$kategori = isset($_POST['kategori']) ? trim($_POST['kategori']) : '';
$lat = isset($_POST['latitude']) ? floatval($_POST['latitude']) : 0.0;
$lng = isset($_POST['longitude']) ? floatval($_POST['longitude']) : 0.0;
if (empty($nama) || $lat == 0.0 || $lng == 0.0) {
http_response_code(400);
echo "Nama SPBU dan lokasi koordinat wajib diisi!";
exit;
}
$stmt = $conn->prepare("UPDATE spbu_locations SET nama_spbu=?, no_hp=?, kategori=?, latitude=?, longitude=? WHERE id=?");
$stmt->bind_param("sssddi", $nama, $hp, $kategori, $lat, $lng, $id);
if ($stmt->execute()) {
echo "Data berhasil diperbarui!";
} else {
http_response_code(500);
echo "Gagal memperbarui: " . $conn->error;
}
$stmt->close();
} else {
http_response_code(400);
echo "Tabel tidak valid!";
}
$conn->close();
} else {
http_response_code(405);
echo "Metode tidak diizinkan";
}
?>