Initial commit - Fresh Clean Code with User Manual README
This commit is contained in:
@@ -0,0 +1 @@
|
||||
https://sigwebsite.free.je/SPBU_Marker/
|
||||
@@ -0,0 +1,38 @@
|
||||
# ??? Panduan Penggunaan Sistem Informasi Geografis (SIG) - SPBU Marker
|
||||
|
||||
Selamat datang di Panduan Pengguna aplikasi **Sistem Informasi Geografis Pemetaan SPBU**. Aplikasi ini dirancang untuk mempermudah Anda dalam melihat, menambah, mengubah, dan menghapus data marker SPBU secara interaktif di atas peta.
|
||||
|
||||
## ?? Link Akses Website
|
||||
Aplikasi dapat diakses langsung melalui tautan berikut:
|
||||
?? [Demo Live Website SPBU Marker](https://sigwebsite.free.je/SPBU_Marker/)
|
||||
|
||||
---
|
||||
|
||||
## ?? Fitur Utama & Cara Menggunakannya
|
||||
|
||||
### 1. Menampilkan Daftar & Fokus Lokasi SPBU
|
||||
* **Cara Kerja:** Di sebelah kanan layar terdapat panel **"Daftar SPBU Terdaftar"** yang menampilkan semua nama, nomor registrasi, dan alamat SPBU yang ada di database.
|
||||
* **Fitur Utama:** Jika Anda **mengklik salah satu item** pada daftar tersebut, kamera peta akan otomatis melakukan *fly-to* (meluncur/fokus) ke titik lokasi SPBU tersebut dengan tingkat perbesaran yang tinggi (*zoom-in*), dan form input di sebelah kanan akan otomatis berubah menjadi mode edit.
|
||||
|
||||
### 2. Menambah Data SPBU Baru (Lacak Alamat Otomatis)
|
||||
* **Langkah 1:** Klik tombol hijau besar bertuliskan **"TAMBAH SPBU BARU"** di bawah peta. Kursor mouse pada area peta akan berubah menjadi tanda bidik (*crosshair*).
|
||||
* **Langkah 2:** Cari lokasi yang diinginkan pada peta, lalu **klik sekali** pada titik tersebut. Sebuah pin (marker) sementara akan muncul.
|
||||
* **Fitur Cerdas (Geocoding):** Sistem akan otomatis melacak koordinat (Latitude & Longitude) serta mengisi kolom alamat secara otomatis berdasarkan titik yang Anda klik menggunakan layanan *OpenStreetMap Nominatim*.
|
||||
* **Langkah 3:** Lengkapi kolom nama SPBU, nomor register, dan tipe operasional (24 Jam atau Biasa) pada form di sebelah kanan.
|
||||
* **Langkah 4:** Klik tombol **"SIMPAN DATA"** untuk memasukkannya ke database.
|
||||
|
||||
### 3. Mengubah / Mengedit Data SPBU (Interaktif)
|
||||
Ada dua cara mudah untuk mengubah data yang sudah terdaftar:
|
||||
* **Melalui Form:** Klik salah satu SPBU di daftar sebelah kanan, ubah informasi yang diinginkan pada form (seperti mengganti nama atau tipe), lalu klik **"UPDATE DATA SPBU"**.
|
||||
* **Melalui Geser Pin (Drag & Drop):** Anda bisa langsung **mengklik dan menahan (drag)** ikon pin SPBU di atas peta, lalu menggesernya ke lokasi baru. Saat pin dilepas, sistem akan otomatis memperbarui koordinat posisi baru dan melacak ulang alamat terbarunya secara instan.
|
||||
|
||||
### 4. Menghapus Data SPBU
|
||||
* **Cara Kerja:** Klik pin SPBU yang ingin dihapus di atas peta sampai muncul jendela kecil (*popup*).
|
||||
* **Langkah:** Klik tombol merah bertuliskan **"Hapus"** di dalam popup tersebut. Sistem akan menampilkan konfirmasi keamanan sebelum benar-benar menghapus data tersebut dari server.
|
||||
|
||||
---
|
||||
|
||||
## ?? Indikator Warna Pin di Peta
|
||||
Untuk mempermudah visualisasi, tanda pin di peta memiliki dua warna berbeda berdasarkan tipe operasionalnya:
|
||||
* ?? **Pin Hijau:** Menandakan SPBU yang beroperasi penuh **24 Jam**.
|
||||
* ?? **Pin Merah:** Menandakan SPBU dengan jam operasional **Biasa / Standar**.
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// api_spbu.php
|
||||
require_once 'config.php';
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : '';
|
||||
|
||||
if ($method === 'GET' && $action === 'getAll') {
|
||||
$result = $conn->query("SELECT * FROM spbu ORDER BY created_at DESC");
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) { $data[] = $row; }
|
||||
sendResponse(true, 'Data dimuat', $data);
|
||||
}
|
||||
elseif ($method === 'POST' && $action === 'create') {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$stmt = $conn->prepare("INSERT INTO spbu (nama, no_spbu, alamat, tipe, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("ssssdd", $input['nama'], $input['no_spbu'], $input['alamat'], $input['tipe'], $input['latitude'], $input['longitude']);
|
||||
if ($stmt->execute()) sendResponse(true, 'Berhasil simpan', ['id' => $conn->insert_id]);
|
||||
else sendResponse(false, 'Gagal simpan: ' . $stmt->error);
|
||||
}
|
||||
elseif ($method === 'PUT' && $action === 'update') {
|
||||
$id = intval($_GET['id']);
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$stmt = $conn->prepare("UPDATE spbu SET nama=?, no_spbu=?, alamat=?, tipe=?, latitude=?, longitude=? WHERE id=?");
|
||||
$stmt->bind_param("ssssddi", $input['nama'], $input['no_spbu'], $input['alamat'], $input['tipe'], $input['latitude'], $input['longitude'], $id);
|
||||
if ($stmt->execute()) sendResponse(true, 'Berhasil update');
|
||||
else sendResponse(false, 'Gagal update');
|
||||
}
|
||||
elseif ($method === 'DELETE' && $action === 'delete') {
|
||||
$id = intval($_GET['id']);
|
||||
$stmt = $conn->prepare("DELETE FROM spbu WHERE id=?");
|
||||
$stmt->bind_param("i", $id);
|
||||
if ($stmt->execute()) sendResponse(true, 'Berhasil hapus');
|
||||
else sendResponse(false, 'Gagal hapus');
|
||||
}
|
||||
?>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// config.php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
$host = 'localhost';
|
||||
$username = 'root';
|
||||
$password = '';
|
||||
$database = 'gis_management';
|
||||
$port = 3307; // Sesuaikan port XAMPP Anda
|
||||
|
||||
$conn = new mysqli($host, $username, $password, $database, $port);
|
||||
|
||||
if ($conn->connect_error) {
|
||||
die(json_encode(['success' => false, 'message' => 'Koneksi gagal: ' . $conn->connect_error]));
|
||||
}
|
||||
|
||||
$conn->set_charset("utf8");
|
||||
|
||||
function sendResponse($success, $message, $data = null) {
|
||||
echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sistem GIS SPBU - Mandiri</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
/* Mengatur tinggi peta agar serasi dengan sidebar */
|
||||
#map { height: 650px; width: 100%; border-radius: 12px; border: 2px solid #dee2e6; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
|
||||
/* Sidebar diperpanjang ke bawah */
|
||||
.sidebar-panel {
|
||||
background: white; padding: 20px; border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
height: 730px; /* Tinggi total sidebar ditambah */
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
|
||||
/* Kolom Daftar SPBU diperpanjang maksimal */
|
||||
.data-list {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
margin-top: 10px;
|
||||
padding-right: 8px;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
/* Desain item daftar lebih elegan */
|
||||
.data-item {
|
||||
background: #ffffff; padding: 15px; margin-bottom: 12px; border-radius: 10px;
|
||||
cursor: pointer; border: 1px solid #e9ecef; border-left: 6px solid #0d6efd;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
|
||||
}
|
||||
.data-item:hover { background: #f1f4f9; transform: translateX(5px); border-color: #0d6efd; }
|
||||
.data-item strong { color: #333; display: block; margin-bottom: 3px; }
|
||||
.data-item small { color: #6c757d; font-size: 0.85rem; display: block; line-height: 1.3; }
|
||||
|
||||
.is-editing { border-left-color: #ffc107 !important; background: #fffdf5 !important; border-color: #ffc107; }
|
||||
.custom-pin { filter: drop-shadow(0 3px 3px rgba(0,0,0,0.4)); }
|
||||
|
||||
/* Scrollbar kustom agar terlihat modern */
|
||||
.data-list::-webkit-scrollbar { width: 6px; }
|
||||
.data-list::-webkit-scrollbar-track { background: #f1f1f1; }
|
||||
.data-list::-webkit-scrollbar-thumb { background: #ccc; border-radius: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-8">
|
||||
<div id="map" class="mb-3"></div>
|
||||
<div class="bg-white p-3 rounded shadow-sm d-flex justify-content-between align-items-center">
|
||||
<button class="btn btn-success px-4 py-2 fw-bold shadow-sm" onclick="startAdding()">
|
||||
<i class="fas fa-plus-circle me-2"></i> TAMBAH SPBU BARU
|
||||
</button>
|
||||
<div class="text-muted small">
|
||||
<i class="fas fa-info-circle text-primary me-1"></i>
|
||||
<b>Navigasi:</b> Klik item di daftar untuk Edit. Geser pin di peta untuk pindah lokasi.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="sidebar-panel">
|
||||
<div id="formSection">
|
||||
<h5 id="formTitle" class="fw-bold mb-3 text-primary d-flex justify-content-between">
|
||||
<span><i class="fas fa-edit me-2"></i>Form SPBU</span>
|
||||
<button class="btn btn-sm btn-link p-0 text-decoration-none" onclick="resetForm()">Reset</button>
|
||||
</h5>
|
||||
<form id="spbuForm">
|
||||
<input type="hidden" id="spbuId">
|
||||
<div class="mb-2">
|
||||
<label class="small fw-bold text-secondary">Nama SPBU</label>
|
||||
<input type="text" id="namaSpbu" class="form-control form-control-sm border-2" placeholder="Contoh: SPBU 64.001" required>
|
||||
</div>
|
||||
<div class="row mb-2 g-2">
|
||||
<div class="col-7">
|
||||
<label class="small fw-bold text-secondary">No. Register</label>
|
||||
<input type="text" id="noSpbu" class="form-control form-control-sm" placeholder="ID SPBU" required>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<label class="small fw-bold text-secondary">Tipe</label>
|
||||
<select id="tipeSpbu" class="form-select form-select-sm">
|
||||
<option value="24jam">24 Jam</option>
|
||||
<option value="tidak">Biasa</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="small fw-bold text-secondary">Alamat (Geocode Otomatis)</label>
|
||||
<textarea id="alamatSpbu" class="form-control form-control-sm" rows="2" placeholder="Klik lokasi di peta..."></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="small fw-bold text-secondary">Koordinat (Lat, Lng)</label>
|
||||
<input type="text" id="koordinatSpbu" class="form-control form-control-sm bg-light text-muted" readonly>
|
||||
</div>
|
||||
<button type="submit" id="btnSubmit" class="btn btn-primary w-100 fw-bold shadow-sm py-2">SIMPAN DATA</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex justify-content-between align-items-center">
|
||||
<h6 class="fw-bold mb-0"><i class="fas fa-list-ul me-2"></i>Daftar SPBU Terdaftar</h6>
|
||||
<span id="countBadges" class="badge bg-secondary rounded-pill">0</span>
|
||||
</div>
|
||||
|
||||
<div id="spbuList" class="data-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
var map = L.map('map').setView([-0.026330, 109.342519], 13);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||
|
||||
var spbuGroup = L.layerGroup().addTo(map);
|
||||
var tempMarker = null;
|
||||
var isAddingMode = false;
|
||||
|
||||
function getIcon(tipe) {
|
||||
const color = tipe === '24jam' ? '#198754' : '#dc3545';
|
||||
return L.divIcon({
|
||||
html: `<i class="fas fa-map-marker-alt fa-2x custom-pin" style="color: ${color};"></i>`,
|
||||
className: 'custom-div-icon',
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 30],
|
||||
popupAnchor: [0, -30]
|
||||
});
|
||||
}
|
||||
|
||||
async function reverseGeocode(lat, lng) {
|
||||
const target = document.getElementById('alamatSpbu');
|
||||
target.value = "Sedang melacak alamat...";
|
||||
try {
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`);
|
||||
const data = await response.json();
|
||||
target.value = data.display_name || "Alamat tidak ditemukan di sistem";
|
||||
} catch (e) {
|
||||
target.value = "Koneksi ke layanan alamat terputus";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const res = await fetch('api_spbu.php?action=getAll');
|
||||
const json = await res.json();
|
||||
spbuGroup.clearLayers();
|
||||
const listContainer = document.getElementById('spbuList');
|
||||
listContainer.innerHTML = '';
|
||||
document.getElementById('countBadges').innerText = json.data.length;
|
||||
|
||||
json.data.forEach(spbu => {
|
||||
const marker = L.marker([spbu.latitude, spbu.longitude], {
|
||||
icon: getIcon(spbu.tipe),
|
||||
draggable: true
|
||||
}).addTo(spbuGroup);
|
||||
|
||||
marker.on('dragend', async function(e) {
|
||||
const pos = e.target.getLatLng();
|
||||
if(document.getElementById('spbuId').value == spbu.id) {
|
||||
document.getElementById('koordinatSpbu').value = `${pos.lat.toFixed(6)}, ${pos.lng.toFixed(6)}`;
|
||||
reverseGeocode(pos.lat, pos.lng);
|
||||
}
|
||||
await saveUpdate(spbu.id, spbu.nama, spbu.no_spbu, spbu.alamat, spbu.tipe, pos.lat, pos.lng);
|
||||
});
|
||||
|
||||
marker.bindPopup(`<b>${spbu.nama}</b><br><button class="btn btn-danger btn-sm mt-2 w-100" onclick="deleteData(${spbu.id})">Hapus</button>`);
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'data-item';
|
||||
item.id = `item-${spbu.id}`;
|
||||
item.innerHTML = `
|
||||
<strong>${spbu.nama}</strong>
|
||||
<small><i class="fas fa-tag me-1"></i> No: ${spbu.no_spbu}</small>
|
||||
<small><i class="fas fa-map-marked-alt me-1"></i> ${spbu.alamat ? spbu.alamat.substring(0, 60) + '...' : 'Alamat belum diatur'}</small>
|
||||
`;
|
||||
item.onclick = () => prepareEdit(spbu, marker);
|
||||
listContainer.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function startAdding() {
|
||||
resetForm();
|
||||
isAddingMode = true;
|
||||
document.getElementById('map').style.cursor = 'crosshair';
|
||||
}
|
||||
|
||||
map.on('click', function(e) {
|
||||
if (!isAddingMode) return;
|
||||
const {lat, lng} = e.latlng;
|
||||
document.getElementById('koordinatSpbu').value = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
|
||||
reverseGeocode(lat, lng);
|
||||
|
||||
if (tempMarker) map.removeLayer(tempMarker);
|
||||
tempMarker = L.marker([lat, lng], { draggable: true }).addTo(map);
|
||||
|
||||
tempMarker.on('dragend', function(ev) {
|
||||
const p = ev.target.getLatLng();
|
||||
document.getElementById('koordinatSpbu').value = `${p.lat.toFixed(6)}, ${p.lng.toFixed(6)}`;
|
||||
reverseGeocode(p.lat, p.lng);
|
||||
});
|
||||
|
||||
isAddingMode = false;
|
||||
document.getElementById('map').style.cursor = '';
|
||||
});
|
||||
|
||||
function prepareEdit(spbu, marker) {
|
||||
document.querySelectorAll('.data-item').forEach(i => i.classList.remove('is-editing'));
|
||||
document.getElementById(`item-${spbu.id}`).classList.add('is-editing');
|
||||
|
||||
document.getElementById('formTitle').innerHTML = '<span><i class="fas fa-edit me-2"></i>Edit SPBU</span><button class="btn btn-sm btn-link p-0 text-decoration-none" onclick="resetForm()">Batal</button>';
|
||||
document.getElementById('spbuId').value = spbu.id;
|
||||
document.getElementById('namaSpbu').value = spbu.nama;
|
||||
document.getElementById('noSpbu').value = spbu.no_spbu;
|
||||
document.getElementById('alamatSpbu').value = spbu.alamat || '';
|
||||
document.getElementById('tipeSpbu').value = spbu.tipe;
|
||||
document.getElementById('koordinatSpbu').value = `${spbu.latitude}, ${spbu.longitude}`;
|
||||
|
||||
document.getElementById('btnSubmit').innerText = "UPDATE DATA SPBU";
|
||||
document.getElementById('btnSubmit').className = "btn btn-warning w-100 fw-bold py-2";
|
||||
|
||||
map.flyTo([spbu.latitude, spbu.longitude], 17);
|
||||
marker.openPopup();
|
||||
}
|
||||
|
||||
document.getElementById('spbuForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const id = document.getElementById('spbuId').value;
|
||||
const coords = document.getElementById('koordinatSpbu').value.split(', ');
|
||||
|
||||
if(!coords[0]) return alert("Tentukan lokasi di peta terlebih dahulu!");
|
||||
|
||||
const data = {
|
||||
nama: document.getElementById('namaSpbu').value,
|
||||
no_spbu: document.getElementById('noSpbu').value,
|
||||
alamat: document.getElementById('alamatSpbu').value,
|
||||
tipe: document.getElementById('tipeSpbu').value,
|
||||
latitude: coords[0],
|
||||
longitude: coords[1]
|
||||
};
|
||||
|
||||
const url = id ? `api_spbu.php?action=update&id=${id}` : `api_spbu.php?action=create`;
|
||||
await fetch(url, { method: id ? 'PUT' : 'POST', body: JSON.stringify(data) });
|
||||
|
||||
resetForm();
|
||||
loadData();
|
||||
};
|
||||
|
||||
async function saveUpdate(id, nama, no, alamat, tipe, lat, lng) {
|
||||
await fetch(`api_spbu.php?action=update&id=${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ nama, no_spbu: no, alamat, tipe, latitude: lat, longitude: lng })
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteData(id) {
|
||||
if (confirm('Apakah Anda yakin ingin menghapus data SPBU ini dari sistem?')) {
|
||||
await fetch(`api_spbu.php?action=delete&id=${id}`, { method: 'DELETE' });
|
||||
loadData();
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('spbuForm').reset();
|
||||
document.getElementById('spbuId').value = '';
|
||||
document.getElementById('formTitle').innerHTML = '<span><i class="fas fa-edit me-2"></i>Form SPBU</span>';
|
||||
document.getElementById('btnSubmit').innerText = "SIMPAN DATA";
|
||||
document.getElementById('btnSubmit').className = "btn btn-primary w-100 fw-bold py-2";
|
||||
document.querySelectorAll('.data-item').forEach(i => i.classList.remove('is-editing'));
|
||||
if (tempMarker) map.removeLayer(tempMarker);
|
||||
isAddingMode = false;
|
||||
document.getElementById('map').style.cursor = '';
|
||||
}
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user