Initial Commit: Tugas Semua Pertemuan
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
CREATE DATABASE db_spbu;
|
||||
USE db_spbu;
|
||||
|
||||
CREATE TABLE spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_spbu VARCHAR(100),
|
||||
no_spbu VARCHAR(50),
|
||||
status ENUM('Buka 24 Jam', 'Tidak 24 Jam'),
|
||||
latitude DOUBLE,
|
||||
longitude DOUBLE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE jalan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_jalan VARCHAR(100),
|
||||
status ENUM('Nasional','Provinsi','Kabupaten'),
|
||||
panjang DOUBLE,
|
||||
geojson TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE parsil (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
pemilik VARCHAR(100),
|
||||
status ENUM('SHM','HGB','HGU','HP'),
|
||||
luas DOUBLE,
|
||||
geojson TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `jalan_rusak` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`deskripsi` TEXT NOT NULL,
|
||||
`foto` VARCHAR(255) NOT NULL,
|
||||
`latitude` DOUBLE NOT NULL,
|
||||
`longitude` DOUBLE NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if(isset($_POST['id'])){
|
||||
$id = $_POST['id'];
|
||||
|
||||
$stmt = $conn->prepare("DELETE FROM spbu WHERE id=?");
|
||||
$stmt->bind_param("i", $id);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
} else {
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
echo "ID TIDAK DITEMUKAN";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if(isset($_POST['id'])){
|
||||
$id = $_POST['id'];
|
||||
$stmt = $conn->prepare("DELETE FROM jalan WHERE id=?");
|
||||
$stmt->bind_param("i", $id);
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
} else {
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
echo "ID TIDAK DITEMUKAN";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = intval($_POST['id']);
|
||||
$foto = mysqli_real_escape_string($conn, $_POST['foto']);
|
||||
|
||||
// Hapus file foto
|
||||
$targetDir = "uploads/";
|
||||
if (!empty($foto) && file_exists($targetDir . $foto)) {
|
||||
unlink($targetDir . $foto);
|
||||
}
|
||||
|
||||
$query = "DELETE FROM jalan_rusak WHERE id=$id";
|
||||
if (mysqli_query($conn, $query)) {
|
||||
echo "success";
|
||||
} else {
|
||||
echo "db_error: " . mysqli_error($conn);
|
||||
}
|
||||
} else {
|
||||
echo "invalid_request";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if(isset($_POST['id'])){
|
||||
$id = $_POST['id'];
|
||||
$stmt = $conn->prepare("DELETE FROM parsil WHERE id=?");
|
||||
$stmt->bind_param("i", $id);
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
} else {
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
echo "ID TIDAK DITEMUKAN";
|
||||
}
|
||||
?>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$result = mysqli_query($conn, "SELECT * FROM jalan");
|
||||
$data = [];
|
||||
|
||||
while($row = mysqli_fetch_assoc($result)){
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode($data);
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
$result = mysqli_query($conn, "SELECT * FROM jalan_rusak ORDER BY created_at DESC");
|
||||
$data = [];
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
$data[] = $row;
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$result = mysqli_query($conn, "SELECT * FROM parsil");
|
||||
$data = [];
|
||||
|
||||
while($row = mysqli_fetch_assoc($result)){
|
||||
$data[] = $row;
|
||||
}
|
||||
|
||||
echo json_encode($data);
|
||||
?>
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
// Baca file GeoJSON
|
||||
$file = 'geojson/Jaringan_Jalan.json';
|
||||
|
||||
if (!file_exists($file)) {
|
||||
die("❌ File tidak ditemukan!");
|
||||
}
|
||||
|
||||
$json = file_get_contents($file);
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (!$data || !isset($data['features'])) {
|
||||
die("❌ Format GeoJSON tidak valid!");
|
||||
}
|
||||
|
||||
// Prepare statement (AMAN)
|
||||
$stmt = $conn->prepare("
|
||||
INSERT INTO jalan (nama_jalan, status, panjang, geojson)
|
||||
VALUES (?, ?, ?, ?)
|
||||
");
|
||||
|
||||
$total = 0;
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ($data['features'] as $feature) {
|
||||
$total++;
|
||||
|
||||
// Ambil data dari properties
|
||||
$nama = $feature['properties']['nama_jalan'] ?? 'Tanpa Nama';
|
||||
$status = $feature['properties']['status_jal'] ?? 'Kabupaten';
|
||||
$panjang = $feature['properties']['Plan_Len'] ?? 0;
|
||||
|
||||
// Validasi status (biar sesuai ENUM DB)
|
||||
$allowed_status = ['Nasional', 'Provinsi', 'Kabupaten', 'Kota'];
|
||||
if (!in_array($status, $allowed_status)) {
|
||||
$status = 'Kabupaten';
|
||||
}
|
||||
|
||||
// Ambil koordinat
|
||||
if (!isset($feature['geometry']['coordinates'])) {
|
||||
$failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$coordinates = $feature['geometry']['coordinates'];
|
||||
|
||||
// Convert dari [lng, lat] → [lat, lng]
|
||||
$geo = [];
|
||||
foreach ($coordinates as $coord) {
|
||||
if (count($coord) < 2) continue;
|
||||
$geo[] = [$coord[1], $coord[0]];
|
||||
}
|
||||
|
||||
if (count($geo) < 2) {
|
||||
$failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$geojson = json_encode($geo);
|
||||
|
||||
// Bind & execute
|
||||
$stmt->bind_param("ssds", $nama, $status, $panjang, $geojson);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$success++;
|
||||
} else {
|
||||
$failed++;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
|
||||
// Output hasil
|
||||
echo "<h2>✅ Import Selesai</h2>";
|
||||
echo "Total data: $total <br>";
|
||||
echo "Berhasil: $success <br>";
|
||||
echo "Gagal: $failed <br>";
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$nama = $_POST['nama'] ?? '';
|
||||
$no = $_POST['no'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
$lat = $_POST['lat'] ?? '';
|
||||
$lng = $_POST['lng'] ?? '';
|
||||
|
||||
// VALIDASI
|
||||
if($nama == "" || $no == "" || $status == "" || $lat == "" || $lng == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
// PREPARED STATEMENT
|
||||
$stmt = $conn->prepare("INSERT INTO spbu (nama_spbu, no_spbu, status, latitude, longitude) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("sssdd", $nama, $no, $status, $lat, $lng);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
}else{
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
// AMBIL DATA
|
||||
$nama = $_POST['nama'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
$panjang = $_POST['panjang'] ?? '';
|
||||
$geojson = $_POST['geojson'] ?? '';
|
||||
|
||||
// VALIDASI DASAR
|
||||
if($nama == "" || $status == "" || $panjang == "" || $geojson == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
if(strlen($geojson) < 10){
|
||||
echo "GEOJSON ERROR / TERLALU PENDEK";
|
||||
exit;
|
||||
}
|
||||
|
||||
// PREPARED STATEMENT (lebih aman dari SQL injection)
|
||||
$stmt = $conn->prepare("INSERT INTO jalan (nama_jalan, status, panjang, geojson) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("ssds", $nama, $status, $panjang, $geojson);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
}else{
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$deskripsi = mysqli_real_escape_string($conn, $_POST['deskripsi']);
|
||||
$lat = floatval($_POST['lat']);
|
||||
$lng = floatval($_POST['lng']);
|
||||
|
||||
// Upload foto
|
||||
$targetDir = "uploads/";
|
||||
if (!file_exists($targetDir)) {
|
||||
mkdir($targetDir, 0777, true);
|
||||
}
|
||||
$fileName = time() . '_' . basename($_FILES["foto"]["name"]);
|
||||
$targetFilePath = $targetDir . $fileName;
|
||||
$fileType = strtolower(pathinfo($targetFilePath, PATHINFO_EXTENSION));
|
||||
$allowedTypes = array('jpg', 'jpeg', 'png', 'gif');
|
||||
|
||||
if (in_array($fileType, $allowedTypes)) {
|
||||
if (move_uploaded_file($_FILES["foto"]["tmp_name"], $targetFilePath)) {
|
||||
$query = "INSERT INTO jalan_rusak (deskripsi, foto, latitude, longitude) VALUES ('$deskripsi', '$fileName', $lat, $lng)";
|
||||
if (mysqli_query($conn, $query)) {
|
||||
echo "success";
|
||||
} else {
|
||||
echo "db_error: " . mysqli_error($conn);
|
||||
}
|
||||
} else {
|
||||
echo "upload_failed";
|
||||
}
|
||||
} else {
|
||||
echo "invalid_format";
|
||||
}
|
||||
} else {
|
||||
echo "invalid_request";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$pemilik = $_POST['pemilik'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
$luas = $_POST['luas'] ?? '';
|
||||
$geojson = $_POST['geojson'] ?? '';
|
||||
|
||||
// VALIDASI
|
||||
if($pemilik == "" || $status == "" || $luas == "" || $geojson == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
// PREPARED STATEMENT
|
||||
$stmt = $conn->prepare("INSERT INTO parsil (pemilik, status, luas, geojson) VALUES (?, ?, ?, ?)");
|
||||
$stmt->bind_param("ssds", $pemilik, $status, $luas, $geojson);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
}else{
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
$conn = mysqli_connect("127.0.0.1", "root", "", "db_spbu", 3307);
|
||||
|
||||
if (!$conn) {
|
||||
die("Koneksi gagal: " . mysqli_connect_error());
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$type = $_POST['type'];
|
||||
$coords = $_POST['coords'];
|
||||
|
||||
mysqli_query($conn, "INSERT INTO shapes (type, coordinates)
|
||||
VALUES ('$type','$coords')");
|
||||
|
||||
echo "success";
|
||||
?>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base target="_top">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Quick Start - Leaflet</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
height: 400px;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div id="map" style="width: 600px; height: 400px;"></div>
|
||||
<script>
|
||||
|
||||
const map = L.map('map').setView([-0.05559005, 109.3486106], 17);
|
||||
|
||||
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
|
||||
const marker = L.marker([-0.05559005, 109.3486106]).addTo(map)
|
||||
.bindPopup('<b>Hello world!</b><br />I am a popup.').openPopup();
|
||||
|
||||
const circle = L.circle([-0.051928, 109.358382], {
|
||||
color: 'red',
|
||||
fillColor: '#f03',
|
||||
fillOpacity: 0.5,
|
||||
radius: 500
|
||||
}).addTo(map).bindPopup('I am a circle.');
|
||||
|
||||
const polygon = L.polygon([
|
||||
[-0.056026, 109.356301],
|
||||
[-0.0474, 109.357696],
|
||||
[-0.054975, 109.361751]
|
||||
]).addTo(map).bindPopup('I am a polygon.');
|
||||
|
||||
|
||||
const popup = L.popup()
|
||||
.setLatLng([-0.05559005, 109.3486106])
|
||||
.setContent('I am a standalone popup.')
|
||||
.openOn(map);
|
||||
|
||||
function onMapClick(e) {
|
||||
popup
|
||||
.setLatLng(e.latlng)
|
||||
.setContent(`You clicked the map at ${e.latlng.toString()}`)
|
||||
.openOn(map);
|
||||
}
|
||||
|
||||
map.on('click', onMapClick);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_POST['id'] ?? '';
|
||||
$lat = $_POST['lat'] ?? '';
|
||||
$lng = $_POST['lng'] ?? '';
|
||||
|
||||
if($id == "" || $lat == "" || $lng == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE spbu SET latitude=?, longitude=? WHERE id=?");
|
||||
$stmt->bind_param("ddi", $lat, $lng, $id);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "OK";
|
||||
}else{
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_POST['id'] ?? '';
|
||||
$nama = $_POST['nama'] ?? '';
|
||||
$no = $_POST['no'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
|
||||
if($id == "" || $nama == "" || $no == "" || $status == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE spbu SET nama_spbu=?, no_spbu=?, status=? WHERE id=?");
|
||||
$stmt->bind_param("sssi", $nama, $no, $status, $id);
|
||||
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
}else{
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_POST['id'] ?? '';
|
||||
$nama = $_POST['nama'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
|
||||
if($id == "" || $nama == "" || $status == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE jalan SET nama_jalan=?, status=? WHERE id=?");
|
||||
$stmt->bind_param("ssi", $nama, $status, $id);
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
} else {
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = intval($_POST['id']);
|
||||
$deskripsi = mysqli_real_escape_string($conn, $_POST['deskripsi']);
|
||||
$fotoLama = mysqli_real_escape_string($conn, $_POST['foto_lama']);
|
||||
|
||||
// Jika ada upload foto baru
|
||||
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||
$targetDir = "uploads/";
|
||||
$fileName = time() . '_' . basename($_FILES["foto"]["name"]);
|
||||
$targetFilePath = $targetDir . $fileName;
|
||||
$fileType = strtolower(pathinfo($targetFilePath, PATHINFO_EXTENSION));
|
||||
$allowedTypes = array('jpg', 'jpeg', 'png', 'gif');
|
||||
|
||||
if (in_array($fileType, $allowedTypes)) {
|
||||
if (move_uploaded_file($_FILES["foto"]["tmp_name"], $targetFilePath)) {
|
||||
// Hapus foto lama jika ada
|
||||
if (!empty($fotoLama) && file_exists($targetDir . $fotoLama)) {
|
||||
unlink($targetDir . $fotoLama);
|
||||
}
|
||||
$query = "UPDATE jalan_rusak SET deskripsi='$deskripsi', foto='$fileName' WHERE id=$id";
|
||||
} else {
|
||||
echo "upload_failed";
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo "invalid_format";
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Update tanpa ganti foto
|
||||
$query = "UPDATE jalan_rusak SET deskripsi='$deskripsi' WHERE id=$id";
|
||||
}
|
||||
|
||||
// Juga bisa update posisi jika dikirim dari drag marker
|
||||
if (isset($_POST['lat']) && isset($_POST['lng'])) {
|
||||
$lat = floatval($_POST['lat']);
|
||||
$lng = floatval($_POST['lng']);
|
||||
$query = "UPDATE jalan_rusak SET deskripsi='$deskripsi', latitude=$lat, longitude=$lng" . (isset($fileName) ? ", foto='$fileName'" : "") . " WHERE id=$id";
|
||||
}
|
||||
|
||||
if (mysqli_query($conn, $query)) {
|
||||
echo "success";
|
||||
} else {
|
||||
echo "db_error: " . mysqli_error($conn);
|
||||
}
|
||||
} else {
|
||||
echo "invalid_request";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
include 'koneksi.php';
|
||||
|
||||
$id = $_POST['id'] ?? '';
|
||||
$pemilik = $_POST['pemilik'] ?? '';
|
||||
$status = $_POST['status'] ?? '';
|
||||
|
||||
if($id == "" || $pemilik == "" || $status == ""){
|
||||
echo "DATA TIDAK LENGKAP";
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE parsil SET pemilik=?, status=? WHERE id=?");
|
||||
$stmt->bind_param("ssi", $pemilik, $status, $id);
|
||||
if($stmt->execute()){
|
||||
echo "success";
|
||||
} else {
|
||||
echo "MYSQL ERROR: " . $stmt->error;
|
||||
}
|
||||
$stmt->close();
|
||||
?>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,5 @@
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3307
|
||||
DB_NAME=spbu_db
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: POST");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
// Cek apakah user sudah login sebagai admin
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (
|
||||
!empty($data->nama) &&
|
||||
!empty($data->no_spbu) &&
|
||||
!empty($data->status) &&
|
||||
!empty($data->latitude) &&
|
||||
!empty($data->longitude)
|
||||
) {
|
||||
try {
|
||||
$query = "INSERT INTO spbu (nama, no_spbu, status, latitude, longitude) VALUES (:nama, :no_spbu, :status, :latitude, :longitude)";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":nama", $data->nama);
|
||||
$stmt->bindParam(":no_spbu", $data->no_spbu);
|
||||
$stmt->bindParam(":status", $data->status);
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(201);
|
||||
echo json_encode(array(
|
||||
"message" => "SPBU berhasil ditambahkan",
|
||||
"id" => $db->lastInsertId()
|
||||
));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menambahkan SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Data tidak lengkap"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"is_admin" => true,
|
||||
"username" => $_SESSION['admin_username']
|
||||
));
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"is_admin" => false
|
||||
));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: DELETE");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "DELETE FROM spbu WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil dihapus"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menghapus SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?><?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: DELETE");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "DELETE FROM spbu WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil dihapus"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menghapus SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
require_once '../config/database.php';
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
try {
|
||||
$query = "SELECT id, nama, no_spbu, status, latitude, longitude FROM spbu ORDER BY nama ASC";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute();
|
||||
|
||||
$spbu_arr = array();
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$spbu_item = array(
|
||||
"id" => $row['id'],
|
||||
"nama" => $row['nama'],
|
||||
"no_spbu" => $row['no_spbu'],
|
||||
"status" => $row['status'],
|
||||
"latitude" => floatval($row['latitude']),
|
||||
"longitude" => floatval($row['longitude'])
|
||||
);
|
||||
array_push($spbu_arr, $spbu_item);
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode($spbu_arr);
|
||||
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: POST");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->username) && !empty($data->password)) {
|
||||
try {
|
||||
$query = "SELECT id, username, password FROM admin WHERE username = :username LIMIT 1";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":username", $data->username);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (password_verify($data->password, $row['password'])) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
$_SESSION['admin_id'] = $row['id'];
|
||||
$_SESSION['admin_username'] = $row['username'];
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"message" => "Login berhasil",
|
||||
"username" => $row['username']
|
||||
));
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Password salah"));
|
||||
}
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Username tidak ditemukan"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Username dan password diperlukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
session_start();
|
||||
session_destroy();
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "Logout berhasil"));
|
||||
?>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: PUT");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id) && !empty($data->latitude) && !empty($data->longitude)) {
|
||||
try {
|
||||
$query = "UPDATE spbu SET latitude = :latitude, longitude = :longitude WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "Posisi SPBU berhasil diupdate"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal mengupdate posisi"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Data tidak lengkap"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: PUT");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "UPDATE spbu SET nama = :nama, no_spbu = :no_spbu, status = :status, latitude = :latitude, longitude = :longitude WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":nama", $data->nama);
|
||||
$stmt->bindParam(":no_spbu", $data->no_spbu);
|
||||
$stmt->bindParam(":status", $data->status);
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil diupdate"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal mengupdate SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
class Database {
|
||||
private $host;
|
||||
private $port;
|
||||
private $db_name;
|
||||
private $username;
|
||||
private $password;
|
||||
public $conn;
|
||||
|
||||
public function __construct() {
|
||||
// Load environment variables dari file .env
|
||||
$this->loadEnv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables dari file .env
|
||||
*/
|
||||
private function loadEnv() {
|
||||
$envFile = dirname(__DIR__) . '/.env';
|
||||
|
||||
if (!file_exists($envFile)) {
|
||||
// Jika .env tidak ada, coba copy dari .env.example
|
||||
$envExample = dirname(__DIR__) . '/.env.example';
|
||||
if (file_exists($envExample)) {
|
||||
copy($envExample, $envFile);
|
||||
} else {
|
||||
die('File .env tidak ditemukan! Silakan buat file .env berdasarkan .env.example');
|
||||
}
|
||||
}
|
||||
|
||||
// Baca file .env
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Skip komentar
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key=value
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
$value = trim($value, '"\'');
|
||||
$env[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Set database configuration
|
||||
$this->host = $env['DB_HOST'] ?? 'localhost';
|
||||
$this->port = $env['DB_PORT'] ?? '3307';
|
||||
$this->db_name = $env['DB_NAME'] ?? 'spbu_db';
|
||||
$this->username = $env['DB_USER'] ?? 'root';
|
||||
$this->password = $env['DB_PASSWORD'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
public function getConnection() {
|
||||
$this->conn = null;
|
||||
try {
|
||||
$dsn = "mysql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->db_name;
|
||||
|
||||
$this->conn = new PDO(
|
||||
$dsn,
|
||||
$this->username,
|
||||
$this->password,
|
||||
array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
|
||||
)
|
||||
);
|
||||
} catch(PDOException $e) {
|
||||
// Log error daripada menampilkannya (untuk production)
|
||||
error_log("Database Connection Error: " . $e->getMessage());
|
||||
|
||||
// Untuk development, tampilkan error
|
||||
if ($this->isDevelopment()) {
|
||||
die("Connection error: " . $e->getMessage());
|
||||
} else {
|
||||
die("Database connection failed. Please try again later.");
|
||||
}
|
||||
}
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in development mode
|
||||
*/
|
||||
private function isDevelopment() {
|
||||
return true; // Set false untuk production
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment value
|
||||
*/
|
||||
public function getEnv($key, $default = null) {
|
||||
return $this->$key ?? $default;
|
||||
}
|
||||
}
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=spbu_db
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
+423
@@ -0,0 +1,423 @@
|
||||
# 🗺️ WebGIS SPBU - Sistem Informasi Lokasi Stasiun Pengisian Bahan Bakar Umum
|
||||
|
||||
Sistem Informasi Geografis berbasis web untuk memvisualisasikan dan mengelola data Stasiun Pengisian Bahan Bakar Umum (SPBU) di Pontianak. Dibangun menggunakan Leaflet.js sebagai peta interaktif, PHP sebagai backend, dan MySQL sebagai database.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Fitur Utama
|
||||
|
||||
### 🗺️ Visualisasi Peta
|
||||
|
||||
* Peta interaktif menggunakan Leaflet.js
|
||||
* Tile map dari CartoDB Voyager
|
||||
* Marker kustom untuk setiap SPBU
|
||||
* Popup informasi detail SPBU
|
||||
* Zoom dan navigasi yang smooth
|
||||
|
||||
### 📊 Dashboard Sidebar
|
||||
|
||||
* Statistik total SPBU
|
||||
* Jumlah SPBU 24 Jam
|
||||
* Daftar SPBU yang dapat dicari
|
||||
* Klik untuk zoom ke lokasi SPBU
|
||||
|
||||
### 🔐 Sistem Autentikasi
|
||||
|
||||
* Login admin dengan session PHP
|
||||
* Password hashing menggunakan bcrypt
|
||||
* Protected API endpoints
|
||||
* Auto-logout saat session expired
|
||||
* Logout manual dengan konfirmasi
|
||||
|
||||
### ✏️ Manajemen Data (Admin Only)
|
||||
|
||||
* **Tambah SPBU**: Mode pilih titik di peta → isi form → simpan
|
||||
* **Edit SPBU**: Update nama, nomor, status, dan koordinat
|
||||
* **Hapus SPBU**: Hapus dengan konfirmasi
|
||||
* **Pindah Lokasi**: Drag & drop marker untuk update koordinat
|
||||
* **Auto-save**: Perubahan langsung tersimpan ke database
|
||||
|
||||
### 🎨 Visual Marker
|
||||
|
||||
* **SPBU 24 Jam**: Marker berwarna **Biru** (`#3b82f6`)
|
||||
* **SPBU Terbatas**: Marker berwarna **Hijau** (`#10b981`)
|
||||
* Animasi pulse pada marker sementara saat memilih lokasi
|
||||
|
||||
### 🔍 Pencarian
|
||||
|
||||
* Search bar di header dan sidebar mobile
|
||||
* Filter berdasarkan nama atau nomor SPBU
|
||||
* Real-time filtering
|
||||
* Klik hasil untuk zoom ke lokasi
|
||||
|
||||
---
|
||||
|
||||
## 📁 Struktur Project
|
||||
|
||||
```text
|
||||
SPBU/
|
||||
│
|
||||
├── index.php # Main application file
|
||||
│
|
||||
├── config/
|
||||
│ └── database.php # Database configuration & connection
|
||||
│
|
||||
├── api/
|
||||
│ ├── get_spbu.php # GET - Fetch all SPBU data
|
||||
│ ├── add_spbu.php # POST - Add new SPBU
|
||||
│ ├── update_spbu.php # PUT - Update SPBU data
|
||||
│ ├── update_position.php # PUT - Update marker position only
|
||||
│ ├── delete_spbu.php # DELETE - Remove SPBU
|
||||
│ ├── login.php # POST - Admin authentication
|
||||
│ ├── logout.php # GET - Destroy session
|
||||
│ └── check_session.php # GET - Check admin session status
|
||||
│
|
||||
├── database/
|
||||
│ └── spbu_db.sql # Database schema & sample data
|
||||
│
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Schema
|
||||
|
||||
### Tabel: `admin`
|
||||
|
||||
| Kolom | Tipe | Deskripsi |
|
||||
| ---------- | ------------ | ------------------------ |
|
||||
| id | INT (PK) | Auto-increment ID |
|
||||
| username | VARCHAR(50) | Username admin (unique) |
|
||||
| password | VARCHAR(255) | Password (bcrypt hashed) |
|
||||
| created_at | TIMESTAMP | Waktu pembuatan akun |
|
||||
|
||||
### Tabel: `spbu`
|
||||
|
||||
| Kolom | Tipe | Deskripsi |
|
||||
| ---------- | ------------- | ---------------------------- |
|
||||
| id | INT (PK) | Auto-increment ID |
|
||||
| nama | VARCHAR(200) | Nama SPBU |
|
||||
| no_spbu | VARCHAR(50) | Nomor registrasi SPBU |
|
||||
| status | ENUM | '24jam' atau 'terbatas' |
|
||||
| latitude | DECIMAL(10,7) | Koordinat latitude |
|
||||
| longitude | DECIMAL(10,7) | Koordinat longitude |
|
||||
| created_at | TIMESTAMP | Waktu pembuatan data |
|
||||
| updated_at | TIMESTAMP | Waktu update terakhir (auto) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Instalasi
|
||||
|
||||
### Prasyarat
|
||||
|
||||
* **Web Server**: Apache 2.4+ atau Nginx
|
||||
* **PHP**: Versi 7.4 atau lebih tinggi
|
||||
* **MySQL**: Versi 5.7 atau lebih tinggi
|
||||
* **PHP Extensions**: PDO, PDO_MySQL, JSON, Session
|
||||
|
||||
### Langkah Instalasi
|
||||
|
||||
#### 1. Clone atau Download Project
|
||||
|
||||
```bash
|
||||
git clone https://git.ifuntanhub.dev/FH/WebGIS-Tugas-Setiap-Pertemuan.git
|
||||
cd SPBU
|
||||
```
|
||||
|
||||
Atau download ZIP dan extract ke folder web server Anda (misal: `htdocs/SPBU`).
|
||||
|
||||
#### 2. Setup Database
|
||||
|
||||
```bash
|
||||
# Login ke MySQL
|
||||
mysql -u root -p
|
||||
|
||||
# Import database
|
||||
source /path/to/database/spbu_db.sql
|
||||
```
|
||||
|
||||
Atau melalui phpMyAdmin:
|
||||
|
||||
* Buat database baru: `spbu_db`
|
||||
* Import file `database/spbu_db.sql`
|
||||
|
||||
#### 3. Konfigurasi Database
|
||||
|
||||
Edit file `config/database.php`:
|
||||
|
||||
```php
|
||||
class Database {
|
||||
private $host = "localhost"; // Host database
|
||||
private $db_name = "spbu_db"; // Nama database
|
||||
private $username = "root"; // Username MySQL
|
||||
private $password = ""; // Password MySQL
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Set Folder Permissions
|
||||
|
||||
```bash
|
||||
# Pastikan web server bisa membaca semua file
|
||||
chmod -R 755 webgis-spbu/
|
||||
```
|
||||
|
||||
#### 5. Akses Aplikasi
|
||||
|
||||
Buka browser dan akses:
|
||||
|
||||
```text
|
||||
http://localhost/SPBU/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Login Admin
|
||||
|
||||
### Default Credentials
|
||||
|
||||
* Username: `admin`
|
||||
* Password: `admin123`
|
||||
|
||||
⚠️ **Keamanan:** Ganti password default segera setelah instalasi!
|
||||
|
||||
Gunakan password hash generator bcrypt untuk membuat password baru.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Panduan Penggunaan
|
||||
|
||||
### Mode Normal (Public)
|
||||
|
||||
* Melihat SPBU: Buka peta, semua marker SPBU akan ditampilkan
|
||||
* Info Detail: Klik marker untuk melihat informasi SPBU (nama, nomor, status, koordinat)
|
||||
* Pencarian: Gunakan search bar di header untuk mencari SPBU
|
||||
* Navigasi: Klik item di sidebar untuk zoom ke lokasi SPBU
|
||||
* Statistik: Lihat total SPBU dan jumlah SPBU 24 jam di sidebar
|
||||
|
||||
### Mode Admin
|
||||
|
||||
#### Login
|
||||
|
||||
Klik tombol "Login" di pojok kanan atas → masukkan kredensial
|
||||
|
||||
#### Tambah SPBU
|
||||
|
||||
* Klik tombol "Tambah" di sidebar
|
||||
* Akan muncul indikator "Mode Pilih Titik"
|
||||
* Klik pada peta untuk memilih lokasi
|
||||
* Isi form yang muncul (nama, nomor, status)
|
||||
* Klik "Simpan"
|
||||
|
||||
#### Edit SPBU
|
||||
|
||||
* Klik marker SPBU
|
||||
* Klik tombol "Edit" di popup
|
||||
* Ubah data yang diperlukan
|
||||
* Klik "Simpan"
|
||||
|
||||
#### Hapus SPBU
|
||||
|
||||
* Klik marker SPBU
|
||||
* Klik tombol "Hapus" di popup
|
||||
* Konfirmasi penghapusan
|
||||
|
||||
#### Pindah Lokasi
|
||||
|
||||
* Tahan (hold) marker yang ingin dipindahkan
|
||||
* Geser (drag) ke lokasi baru
|
||||
* Lepaskan (drop), posisi akan auto-update
|
||||
|
||||
#### Logout
|
||||
|
||||
Klik ikon logout di samping toggle admin
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Kustomisasi
|
||||
|
||||
### Mengubah Lokasi Awal Peta
|
||||
|
||||
Edit di `index.php` pada fungsi `initMap()`:
|
||||
|
||||
```javascript
|
||||
// Format: [latitude, longitude], zoomLevel
|
||||
map = L.map('map', { zoomControl: true }).setView([-0.0263, 109.3425], 13);
|
||||
```
|
||||
|
||||
### Mengubah Warna Marker
|
||||
|
||||
Edit di `index.php` pada fungsi `createSpbuIcon()`:
|
||||
|
||||
```javascript
|
||||
// Warna untuk 24 jam (biru) dan terbatas (hijau)
|
||||
const color = status === '24jam' ? '#3b82f6' : '#10b981';
|
||||
```
|
||||
|
||||
### Mengubah Tile Map
|
||||
|
||||
Edit di `index.php` pada fungsi `initMap()`:
|
||||
|
||||
```javascript
|
||||
// Ganti URL tile provider
|
||||
L.tileLayer('URL_TILE_PROVIDER', { ... }).addTo(map);
|
||||
```
|
||||
|
||||
### Menambah Data Contoh
|
||||
|
||||
Edit file `database/spbu_db.sql` dan tambahkan INSERT statements:
|
||||
|
||||
```sql
|
||||
INSERT INTO spbu (nama, no_spbu, status, latitude, longitude) VALUES
|
||||
('SPBU Contoh', '00.000.00', '24jam', -0.0263, 109.3425);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Keamanan
|
||||
|
||||
Aplikasi ini memiliki beberapa lapisan keamanan:
|
||||
|
||||
* Session PHP: Autentikasi admin menggunakan session
|
||||
* Password Hashing: Password disimpan menggunakan bcrypt
|
||||
* API Protection: Setiap endpoint admin memeriksa session
|
||||
* Input Validation: Validasi input di client dan server side
|
||||
* CORS Headers: Protected API access
|
||||
|
||||
### ⚠️ Rekomendasi Keamanan Tambahan
|
||||
|
||||
* Implementasi CSRF token
|
||||
* Gunakan HTTPS di production
|
||||
* Rate limiting untuk API endpoints
|
||||
* Backup database secara berkala
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Teknologi yang Digunakan
|
||||
|
||||
### Frontend
|
||||
|
||||
* HTML5/CSS3 - Struktur dan styling
|
||||
* Tailwind CSS - Utility-first CSS framework
|
||||
* JavaScript (ES6+) - Interaktivitas dan logika client
|
||||
* Leaflet.js 1.9.4 - Library peta interaktif
|
||||
* CartoDB Voyager - Basemap tiles
|
||||
|
||||
### Backend
|
||||
|
||||
* PHP 7.4+ - Server-side scripting
|
||||
* PDO - Database abstraction layer
|
||||
* MySQL - Relational database
|
||||
|
||||
### Libraries & Plugins
|
||||
|
||||
* Fontsource Inter - Font keluarga Inter
|
||||
* SVG Icons - Heroicons-style icons
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### 1. Database Connection Error
|
||||
|
||||
```text
|
||||
Solusi:
|
||||
- Cek kredensial di config/database.php
|
||||
- Pastikan MySQL service running
|
||||
- Cek nama database sudah benar
|
||||
```
|
||||
|
||||
### 2. Session Tidak Berfungsi
|
||||
|
||||
```text
|
||||
Solusi:
|
||||
- Pastikan folder session PHP writable
|
||||
- Cek konfigurasi session di php.ini
|
||||
- Clear browser cache
|
||||
```
|
||||
|
||||
### 3. Marker Tidak Muncul
|
||||
|
||||
```text
|
||||
Solusi:
|
||||
- Cek console browser untuk error JavaScript
|
||||
- Pastikan API get_spbu.php berfungsi
|
||||
- Cek data di database
|
||||
```
|
||||
|
||||
### 4. Tidak Bisa Login
|
||||
|
||||
```text
|
||||
Solusi:
|
||||
- Cek username & password default
|
||||
- Reset password dengan bcrypt hash generator
|
||||
- Cek session PHP berfungsi
|
||||
```
|
||||
|
||||
### 5. Peta Tidak Loading
|
||||
|
||||
```text
|
||||
Solusi:
|
||||
- Cek koneksi internet (untuk tile map)
|
||||
- Cek tidak ada error JavaScript
|
||||
- Pastikan Leaflet.js loaded dengan benar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 API Documentation
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Endpoint | Auth | Deskripsi |
|
||||
| ------ | ------------------------ | ---- | --------------------------- |
|
||||
| GET | /api/get_spbu.php | No | Mendapatkan semua data SPBU |
|
||||
| POST | /api/add_spbu.php | Yes | Menambah SPBU baru |
|
||||
| PUT | /api/update_spbu.php | Yes | Mengupdate data SPBU |
|
||||
| PUT | /api/update_position.php | Yes | Update posisi marker saja |
|
||||
| DELETE | /api/delete_spbu.php | Yes | Menghapus SPBU |
|
||||
| POST | /api/login.php | No | Login admin |
|
||||
| GET | /api/logout.php | Yes | Logout admin |
|
||||
| GET | /api/check_session.php | No | Cek status session |
|
||||
|
||||
### Contoh Request/Response
|
||||
|
||||
#### Tambah SPBU
|
||||
|
||||
```json
|
||||
POST /api/add_spbu.php
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"nama": "SPBU Contoh",
|
||||
"no_spbu": "00.000.00",
|
||||
"status": "24jam",
|
||||
"latitude": -0.0263,
|
||||
"longitude": 109.3425
|
||||
}
|
||||
```
|
||||
|
||||
Response 201:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "SPBU berhasil ditambahkan",
|
||||
"id": 4
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Changelog
|
||||
|
||||
### Version 1.0.0 (2024)
|
||||
|
||||
* ✨ Initial release
|
||||
* 🗺️ Peta interaktif dengan Leaflet.js
|
||||
* 🔐 Sistem login admin
|
||||
* ✏️ CRUD operasi untuk data SPBU
|
||||
* 🎨 Marker berbeda untuk status SPBU
|
||||
* 🔍 Pencarian dan navigasi
|
||||
* 📊 Dashboard statistik
|
||||
* 🗄️ MySQL database integration
|
||||
* 🔒 Basic security implementation
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: POST");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
// Cek apakah user sudah login sebagai admin
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (
|
||||
!empty($data->nama) &&
|
||||
!empty($data->no_spbu) &&
|
||||
!empty($data->status) &&
|
||||
!empty($data->latitude) &&
|
||||
!empty($data->longitude)
|
||||
) {
|
||||
try {
|
||||
$query = "INSERT INTO spbu (nama, no_spbu, status, latitude, longitude) VALUES (:nama, :no_spbu, :status, :latitude, :longitude)";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":nama", $data->nama);
|
||||
$stmt->bindParam(":no_spbu", $data->no_spbu);
|
||||
$stmt->bindParam(":status", $data->status);
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(201);
|
||||
echo json_encode(array(
|
||||
"message" => "SPBU berhasil ditambahkan",
|
||||
"id" => $db->lastInsertId()
|
||||
));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menambahkan SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Data tidak lengkap"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"is_admin" => true,
|
||||
"username" => $_SESSION['admin_username']
|
||||
));
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"is_admin" => false
|
||||
));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: DELETE");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "DELETE FROM spbu WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil dihapus"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menghapus SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?><?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: DELETE");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "DELETE FROM spbu WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil dihapus"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal menghapus SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
require_once '../config/database.php';
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
try {
|
||||
$query = "SELECT id, nama, no_spbu, status, latitude, longitude FROM spbu ORDER BY nama ASC";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute();
|
||||
|
||||
$spbu_arr = array();
|
||||
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$spbu_item = array(
|
||||
"id" => $row['id'],
|
||||
"nama" => $row['nama'],
|
||||
"no_spbu" => $row['no_spbu'],
|
||||
"status" => $row['status'],
|
||||
"latitude" => floatval($row['latitude']),
|
||||
"longitude" => floatval($row['longitude'])
|
||||
);
|
||||
array_push($spbu_arr, $spbu_item);
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode($spbu_arr);
|
||||
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: POST");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->username) && !empty($data->password)) {
|
||||
try {
|
||||
$query = "SELECT id, username, password FROM admin WHERE username = :username LIMIT 1";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->bindParam(":username", $data->username);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (password_verify($data->password, $row['password'])) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
$_SESSION['admin_id'] = $row['id'];
|
||||
$_SESSION['admin_username'] = $row['username'];
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(array(
|
||||
"message" => "Login berhasil",
|
||||
"username" => $row['username']
|
||||
));
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Password salah"));
|
||||
}
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Username tidak ditemukan"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Username dan password diperlukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
session_start();
|
||||
session_destroy();
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "Logout berhasil"));
|
||||
?>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: PUT");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id) && !empty($data->latitude) && !empty($data->longitude)) {
|
||||
try {
|
||||
$query = "UPDATE spbu SET latitude = :latitude, longitude = :longitude WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "Posisi SPBU berhasil diupdate"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal mengupdate posisi"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "Data tidak lengkap"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
header("Access-Control-Allow-Methods: PUT");
|
||||
header("Access-Control-Max-Age: 3600");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
|
||||
|
||||
session_start();
|
||||
require_once '../config/database.php';
|
||||
|
||||
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
|
||||
http_response_code(401);
|
||||
echo json_encode(array("message" => "Unauthorized"));
|
||||
exit();
|
||||
}
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"));
|
||||
|
||||
if (!empty($data->id)) {
|
||||
try {
|
||||
$query = "UPDATE spbu SET nama = :nama, no_spbu = :no_spbu, status = :status, latitude = :latitude, longitude = :longitude WHERE id = :id";
|
||||
$stmt = $db->prepare($query);
|
||||
|
||||
$stmt->bindParam(":nama", $data->nama);
|
||||
$stmt->bindParam(":no_spbu", $data->no_spbu);
|
||||
$stmt->bindParam(":status", $data->status);
|
||||
$stmt->bindParam(":latitude", $data->latitude);
|
||||
$stmt->bindParam(":longitude", $data->longitude);
|
||||
$stmt->bindParam(":id", $data->id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(200);
|
||||
echo json_encode(array("message" => "SPBU berhasil diupdate"));
|
||||
} else {
|
||||
http_response_code(503);
|
||||
echo json_encode(array("message" => "Gagal mengupdate SPBU"));
|
||||
}
|
||||
} catch(PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(array("message" => "Error: " . $e->getMessage()));
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(array("message" => "ID tidak ditemukan"));
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
class Database {
|
||||
private $host;
|
||||
private $port;
|
||||
private $db_name;
|
||||
private $username;
|
||||
private $password;
|
||||
public $conn;
|
||||
|
||||
public function __construct() {
|
||||
// Load environment variables dari file .env
|
||||
$this->loadEnv();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables dari file .env
|
||||
*/
|
||||
private function loadEnv() {
|
||||
$envFile = dirname(__DIR__) . '/.env';
|
||||
|
||||
if (!file_exists($envFile)) {
|
||||
// Jika .env tidak ada, coba copy dari .env.example
|
||||
$envExample = dirname(__DIR__) . '/.env.example';
|
||||
if (file_exists($envExample)) {
|
||||
copy($envExample, $envFile);
|
||||
} else {
|
||||
die('File .env tidak ditemukan! Silakan buat file .env berdasarkan .env.example');
|
||||
}
|
||||
}
|
||||
|
||||
// Baca file .env
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$env = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Skip komentar
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key=value
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($key, $value) = explode('=', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
$value = trim($value, '"\'');
|
||||
$env[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Set database configuration
|
||||
$this->host = $env['DB_HOST'] ?? 'localhost';
|
||||
$this->port = $env['DB_PORT'] ?? '3306';
|
||||
$this->db_name = $env['DB_NAME'] ?? 'spbu_db';
|
||||
$this->username = $env['DB_USER'] ?? 'root';
|
||||
$this->password = $env['DB_PASSWORD'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
public function getConnection() {
|
||||
$this->conn = null;
|
||||
try {
|
||||
$dsn = "mysql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->db_name;
|
||||
|
||||
$this->conn = new PDO(
|
||||
$dsn,
|
||||
$this->username,
|
||||
$this->password,
|
||||
array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
|
||||
)
|
||||
);
|
||||
} catch(PDOException $e) {
|
||||
// Log error daripada menampilkannya (untuk production)
|
||||
error_log("Database Connection Error: " . $e->getMessage());
|
||||
|
||||
// Untuk development, tampilkan error
|
||||
if ($this->isDevelopment()) {
|
||||
die("Connection error: " . $e->getMessage());
|
||||
} else {
|
||||
die("Database connection failed. Please try again later.");
|
||||
}
|
||||
}
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running in development mode
|
||||
*/
|
||||
private function isDevelopment() {
|
||||
return true; // Set false untuk production
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment value
|
||||
*/
|
||||
public function getEnv($key, $default = null) {
|
||||
return $this->$key ?? $default;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,23 @@
|
||||
CREATE DATABASE IF NOT EXISTS spbu_db;
|
||||
USE spbu_db;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS admin (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO admin (username, password) VALUES
|
||||
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(200) NOT NULL,
|
||||
no_spbu VARCHAR(50) NOT NULL,
|
||||
status ENUM('24jam', 'terbatas') NOT NULL DEFAULT '24jam',
|
||||
latitude DECIMAL(10, 7) NOT NULL,
|
||||
longitude DECIMAL(10, 7) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
+879
@@ -0,0 +1,879 @@
|
||||
<?php
|
||||
session_start();
|
||||
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebGIS SPBU - Sistem Informasi SPBU</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Leaflet CSS & JS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
|
||||
<!-- Fontsource Inter -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-500-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
margin: 16px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-mode .leaflet-marker-draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.admin-mode .leaflet-marker-draggable:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(13, 148, 136, 0.7);
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(13, 148, 136, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(13, 148, 136, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-slate-100 h-screen flex flex-col overflow-hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="relative h-24 flex-shrink-0 bg-cover bg-center flex items-center justify-between px-6 shadow-lg"
|
||||
style="background-image: url('https://image.qwenlm.ai/public_source/f414e259-c5c5-43c4-9181-edf462f60e8b/1c9325cad-a96a-4cea-9742-4d62d18dd61b.png');">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-slate-900/80 to-slate-900/40"></div>
|
||||
<div class="relative z-10 flex items-center gap-3">
|
||||
<div class="bg-teal-500 p-2 rounded-lg shadow-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
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" stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-white tracking-wide">WebGIS SPBU</h1>
|
||||
<p class="text-xs text-teal-200">Sistem Informasi Lokasi Stasiun Pengisian Bahan Bakar Umum</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex items-center gap-4">
|
||||
<!-- Search Bar -->
|
||||
<div class="relative hidden md:block">
|
||||
<input type="text" id="searchInput" placeholder="Cari nama SPBU..."
|
||||
class="w-64 pl-10 pr-4 py-2 rounded-full bg-white/20 border border-white/30 text-white placeholder-teal-200 focus:outline-none focus:ring-2 focus:ring-teal-400 focus:bg-white/30 transition-all text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-teal-200" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Tombol Login / Admin Toggle -->
|
||||
<div id="authContainer">
|
||||
<!-- Akan diisi oleh JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex flex-1 overflow-hidden relative">
|
||||
|
||||
<!-- Sidebar Dashboard -->
|
||||
<aside class="w-96 bg-white border-r border-slate-200 flex flex-col shadow-xl z-20">
|
||||
<!-- Stats -->
|
||||
<div class="p-4 border-b border-slate-100 bg-slate-50">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
|
||||
<p class="text-xs text-slate-500 font-medium">Total SPBU</p>
|
||||
<p class="text-2xl font-bold text-slate-800" id="totalSpbu">0</p>
|
||||
</div>
|
||||
<div class="bg-white p-3 rounded-xl border border-slate-200 shadow-sm">
|
||||
<p class="text-xs text-slate-500 font-medium">Buka 24 Jam</p>
|
||||
<p class="text-2xl font-bold text-teal-600" id="total24Jam">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Search -->
|
||||
<div class="p-4 border-b border-slate-100 md:hidden">
|
||||
<div class="relative">
|
||||
<input type="text" id="searchInputMobile" placeholder="Cari nama SPBU..."
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg bg-slate-100 text-slate-800 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-500 text-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Header -->
|
||||
<div class="px-4 py-3 flex items-center justify-between bg-white border-b border-slate-100">
|
||||
<h2 class="font-semibold text-slate-700 text-sm uppercase tracking-wider">Daftar SPBU</h2>
|
||||
<button id="btnAddSpbu"
|
||||
class="bg-teal-600 hover:bg-teal-700 text-white text-xs font-medium px-3 py-1.5 rounded-lg shadow-sm transition-colors flex items-center gap-1"
|
||||
style="display: none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Tambah
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- SPBU List -->
|
||||
<div id="spbuList" class="flex-1 overflow-y-auto custom-scrollbar p-3 space-y-2">
|
||||
<!-- Items akan di-inject di sini -->
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Map Container -->
|
||||
<div class="flex-1 relative" id="mapContainer">
|
||||
<div id="map"></div>
|
||||
<!-- Map Hint -->
|
||||
<div id="mapHint"
|
||||
class="absolute top-4 right-4 z-[1000] bg-white/90 backdrop-blur px-4 py-2 rounded-lg shadow-md text-xs text-slate-600 border border-slate-200 pointer-events-none"
|
||||
style="display: none;">
|
||||
💡 Tekan tombol tambah pada sidebar untuk menambahkan SPBU
|
||||
</div>
|
||||
<!-- Mode indicator -->
|
||||
<div id="modeIndicator"
|
||||
class="absolute top-4 left-4 z-[1000] bg-teal-600 text-white px-4 py-2 rounded-lg shadow-md text-xs font-medium hidden">
|
||||
🎯 Mode Pilih Titik - Klik pada peta
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Overlay Form Login -->
|
||||
<div id="loginOverlay"
|
||||
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden fade-in">
|
||||
<div class="bg-gradient-to-r from-teal-600 to-teal-500 px-6 py-4">
|
||||
<h3 class="text-lg font-bold text-white">Login Admin</h3>
|
||||
<p class="text-teal-100 text-xs mt-1">Masukkan kredensial untuk mengelola SPBU</p>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<form id="loginForm" class="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Username</label>
|
||||
<input type="text" id="loginUsername" required
|
||||
class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm"
|
||||
placeholder="admin">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Password</label>
|
||||
<input type="password" id="loginPassword" required
|
||||
class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm"
|
||||
placeholder="••••••••">
|
||||
</div>
|
||||
<div id="loginError" class="text-red-500 text-xs hidden">Username atau password salah!</div>
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="button" id="btnCancelLogin"
|
||||
class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button>
|
||||
<button type="submit"
|
||||
class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Masuk</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Form SPBU -->
|
||||
<div id="modalOverlay"
|
||||
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden fade-in">
|
||||
<div class="bg-gradient-to-r from-teal-600 to-teal-500 px-6 py-4 flex items-center justify-between">
|
||||
<h3 class="text-lg font-bold text-white" id="modalTitle">Tambah SPBU Baru</h3>
|
||||
<button id="btnCloseModal" class="text-white/80 hover:text-white transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="spbuForm" class="p-6 space-y-4">
|
||||
<input type="hidden" id="spbuId">
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Nama
|
||||
SPBU</label>
|
||||
<input type="text" id="inputNama" required
|
||||
class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm"
|
||||
placeholder="Contoh: SPBU Pertamina Tebet">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">No.
|
||||
SPBU</label>
|
||||
<input type="text" id="inputNo" required
|
||||
class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm"
|
||||
placeholder="Contoh: 34.101.01">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Status
|
||||
Operasional</label>
|
||||
<select id="inputStatus"
|
||||
class="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent text-sm bg-white">
|
||||
<option value="24jam">Buka 24 Jam</option>
|
||||
<option value="terbatas">Jam Terbatas</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Latitude</label>
|
||||
<input type="number" step="any" id="inputLat" required readonly
|
||||
class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-semibold text-slate-600 mb-1 uppercase tracking-wide">Longitude</label>
|
||||
<input type="number" step="any" id="inputLng" required readonly
|
||||
class="w-full px-3 py-2 border border-slate-200 bg-slate-100 rounded-lg text-sm text-slate-600 cursor-not-allowed">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-2 flex gap-3">
|
||||
<button type="button" id="btnCancelModal"
|
||||
class="flex-1 px-4 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium text-sm transition-colors">Batal</button>
|
||||
<button type="submit"
|
||||
class="flex-1 px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium text-sm transition-colors shadow-md shadow-teal-200">Simpan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// --- State Management ---
|
||||
let spbuData = [];
|
||||
let isAdminMode = <?php echo $isAdmin ? 'true' : 'false'; ?>;
|
||||
let spbuMarkers = {};
|
||||
let map, searchQuery = "";
|
||||
let isSelectingLocation = false;
|
||||
let selectedLatLng = null;
|
||||
let tempMarker = null;
|
||||
|
||||
// --- API Functions ---
|
||||
async function fetchSpbuData() {
|
||||
try {
|
||||
const response = await fetch('api/get_spbu.php');
|
||||
spbuData = await response.json();
|
||||
return spbuData;
|
||||
} catch (error) {
|
||||
console.error('Error fetching SPBU data:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSession() {
|
||||
try {
|
||||
const response = await fetch('api/check_session.php');
|
||||
const data = await response.json();
|
||||
isAdminMode = data.is_admin;
|
||||
return isAdminMode;
|
||||
} catch (error) {
|
||||
console.error('Error checking session:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
try {
|
||||
const response = await fetch('api/login.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
isAdminMode = true;
|
||||
renderAuthContainer();
|
||||
updateAdminUI();
|
||||
document.getElementById('loginOverlay').classList.add('hidden');
|
||||
document.getElementById('loginError').classList.add('hidden');
|
||||
showNotification('Login berhasil! Mode admin diaktifkan.');
|
||||
} else {
|
||||
document.getElementById('loginError').classList.remove('hidden');
|
||||
document.getElementById('loginError').textContent = data.message || 'Login gagal';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during login:', error);
|
||||
showNotification('Error: Gagal terhubung ke server');
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
if (confirm('Apakah Anda yakin ingin logout?')) {
|
||||
try {
|
||||
await fetch('api/logout.php');
|
||||
isAdminMode = false;
|
||||
isSelectingLocation = false;
|
||||
removeTempMarker();
|
||||
map.closePopup();
|
||||
renderAuthContainer();
|
||||
updateAdminUI();
|
||||
showNotification('Logout berhasil! Kembali ke mode normal.');
|
||||
} catch (error) {
|
||||
console.error('Error during logout:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addSpbu(data) {
|
||||
try {
|
||||
const response = await fetch('api/add_spbu.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification('SPBU berhasil ditambahkan!');
|
||||
await refreshData();
|
||||
return result;
|
||||
} else if (response.status === 401) {
|
||||
showNotification('Session expired, silakan login ulang');
|
||||
await logout();
|
||||
} else {
|
||||
showNotification('Error: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding SPBU:', error);
|
||||
showNotification('Error: Gagal menambahkan SPBU');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSpbu(data) {
|
||||
try {
|
||||
const response = await fetch('api/update_spbu.php', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification('SPBU berhasil diupdate!');
|
||||
await refreshData();
|
||||
} else if (response.status === 401) {
|
||||
showNotification('Session expired, silakan login ulang');
|
||||
await logout();
|
||||
} else {
|
||||
showNotification('Error: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating SPBU:', error);
|
||||
showNotification('Error: Gagal mengupdate SPBU');
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePosition(id, latitude, longitude) {
|
||||
try {
|
||||
const response = await fetch('api/update_position.php', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, latitude, longitude })
|
||||
});
|
||||
if (response.ok) {
|
||||
showNotification('Posisi SPBU berhasil diupdate!');
|
||||
} else if (response.status === 401) {
|
||||
showNotification('Session expired, silakan login ulang');
|
||||
await logout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating position:', error);
|
||||
showNotification('Error: Gagal mengupdate posisi');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSpbu(id) {
|
||||
if (confirm('Apakah Anda yakin ingin menghapus SPBU ini?')) {
|
||||
try {
|
||||
const response = await fetch('api/delete_spbu.php', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
});
|
||||
if (response.ok) {
|
||||
showNotification('SPBU berhasil dihapus!');
|
||||
await refreshData();
|
||||
} else if (response.status === 401) {
|
||||
showNotification('Session expired, silakan login ulang');
|
||||
await logout();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting SPBU:', error);
|
||||
showNotification('Error: Gagal menghapus SPBU');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshData() {
|
||||
await fetchSpbuData();
|
||||
renderAllMarkers();
|
||||
renderList();
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// --- Initialize Map ---
|
||||
function initMap() {
|
||||
map = L.map('map', { zoomControl: true }).setView([-0.0263, 109.3425], 13);
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
|
||||
subdomains: 'abcd',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
// Map click untuk memilih lokasi (hanya saat mode pilih lokasi)
|
||||
map.on('click', function (e) {
|
||||
if (isSelectingLocation) {
|
||||
selectLocation(e.latlng);
|
||||
}
|
||||
});
|
||||
|
||||
// Load data awal
|
||||
refreshData().then(() => {
|
||||
renderAuthContainer();
|
||||
updateAdminUI();
|
||||
});
|
||||
}
|
||||
|
||||
function selectLocation(latlng) {
|
||||
selectedLatLng = latlng;
|
||||
|
||||
removeTempMarker();
|
||||
|
||||
tempMarker = L.marker([latlng.lat, latlng.lng], {
|
||||
icon: L.divIcon({
|
||||
className: 'custom-temp-icon',
|
||||
html: `<div style="background-color: #f59e0b; width: 32px; height: 32px; border-radius: 50%; border: 3px solid white; box-shadow: 0 4px 6px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; animation: pulse 2s infinite;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px; color: white;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />
|
||||
</svg>
|
||||
</div>`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16]
|
||||
})
|
||||
}).addTo(map);
|
||||
|
||||
// Buka modal untuk isi data
|
||||
openModalForNewLocation(latlng);
|
||||
}
|
||||
|
||||
function removeTempMarker() {
|
||||
if (tempMarker) {
|
||||
map.removeLayer(tempMarker);
|
||||
tempMarker = null;
|
||||
}
|
||||
}
|
||||
|
||||
function openModalForNewLocation(latlng) {
|
||||
const modal = document.getElementById('modalOverlay');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const form = document.getElementById('spbuForm');
|
||||
|
||||
form.reset();
|
||||
document.getElementById('spbuId').value = '';
|
||||
title.textContent = 'Tambah SPBU Baru';
|
||||
document.getElementById('inputLat').value = latlng.lat.toFixed(7);
|
||||
document.getElementById('inputLng').value = latlng.lng.toFixed(7);
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// --- Render All Markers ---
|
||||
function renderAllMarkers() {
|
||||
Object.values(spbuMarkers).forEach(m => map.removeLayer(m));
|
||||
spbuMarkers = {};
|
||||
|
||||
spbuData.forEach(spbu => {
|
||||
const icon = createSpbuIcon(spbu.status);
|
||||
|
||||
const marker = L.marker([spbu.latitude, spbu.longitude], {
|
||||
icon: icon,
|
||||
draggable: isAdminMode
|
||||
}).addTo(map);
|
||||
|
||||
marker.on('dragend', function (e) {
|
||||
const newPos = e.target.getLatLng();
|
||||
updatePosition(spbu.id, newPos.lat, newPos.lng);
|
||||
|
||||
// Update data lokal
|
||||
spbu.latitude = newPos.lat;
|
||||
spbu.longitude = newPos.lng;
|
||||
marker.setPopupContent(createPopupContent(spbu));
|
||||
renderList();
|
||||
updateStats();
|
||||
});
|
||||
|
||||
const popupContent = createPopupContent(spbu);
|
||||
marker.bindPopup(popupContent, { maxWidth: 300 });
|
||||
spbuMarkers[spbu.id] = marker;
|
||||
});
|
||||
}
|
||||
|
||||
function createSpbuIcon(status = 'terbatas') {
|
||||
const color = status === '24jam' ? '#3b82f6' : '#10b981'; // Biru untuk 24 jam, Hijau untuk terbatas
|
||||
|
||||
return L.divIcon({
|
||||
className: 'custom-spbu-icon',
|
||||
html: `<div style="background-color: ${color}; width: 32px; height: 32px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); border: 3px solid white; box-shadow: 0 4px 6px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="transform: rotate(45deg); width: 16px; height: 16px; color: white;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [0, -32]
|
||||
});
|
||||
}
|
||||
|
||||
function createPopupContent(spbu) {
|
||||
return `
|
||||
<div class="min-w-[200px]">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<h3 class="font-bold text-slate-800 text-base leading-tight pr-2">${spbu.nama}</h3>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full ${spbu.status === '24jam' ? 'bg-teal-100 text-teal-700' : 'bg-amber-100 text-amber-700'} whitespace-nowrap">
|
||||
${spbu.status === '24jam' ? '24 Jam' : 'Terbatas'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1 text-sm text-slate-600 mb-3">
|
||||
<p><span class="font-medium text-slate-500">No. SPBU:</span> ${spbu.no_spbu}</p>
|
||||
<p class="text-xs text-slate-400">📍 ${spbu.latitude.toFixed(5)}, ${spbu.longitude.toFixed(5)}</p>
|
||||
</div>
|
||||
${isAdminMode ? `
|
||||
<div class="flex gap-2 pt-2 border-t border-slate-100">
|
||||
<button onclick="editSpbu(${spbu.id})" class="flex-1 bg-blue-50 hover:bg-blue-100 text-blue-600 text-xs font-semibold py-1.5 rounded-md transition-colors flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
||||
Edit
|
||||
</button>
|
||||
<button onclick="deleteSpbu(${spbu.id})" class="flex-1 bg-red-50 hover:bg-red-100 text-red-600 text-xs font-semibold py-1.5 rounded-md transition-colors flex items-center justify-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
Hapus
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 mt-2 text-center">💡 Tahan & geser marker untuk pindahkan lokasi</p>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// --- Render Sidebar List ---
|
||||
function renderList() {
|
||||
const listContainer = document.getElementById('spbuList');
|
||||
const filteredData = spbuData.filter(spbu =>
|
||||
spbu.nama.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
spbu.no_spbu.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
if (filteredData.length === 0) {
|
||||
listContainer.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-center py-10 text-slate-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mb-2 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p class="text-sm font-medium">Tidak ada SPBU ditemukan</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
listContainer.innerHTML = filteredData.map(spbu => `
|
||||
<div class="bg-white border border-slate-200 rounded-xl p-3 hover:border-teal-400 hover:shadow-md transition-all cursor-pointer group" onclick="zoomToSpbu(${spbu.id})">
|
||||
<div class="flex items-start justify-between mb-1">
|
||||
<h4 class="font-semibold text-slate-800 text-sm group-hover:text-teal-700 transition-colors">${spbu.nama}</h4>
|
||||
<span class="text-[10px] px-2 py-0.5 rounded-full ${spbu.status === '24jam' ? 'bg-teal-100 text-teal-700' : 'bg-amber-100 text-amber-700'} font-medium">
|
||||
${spbu.status === '24jam' ? '24 Jam' : 'Terbatas'}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 mb-1">No: ${spbu.no_spbu}</p>
|
||||
<p class="text-[10px] text-slate-400 font-mono">📍 ${spbu.latitude.toFixed(4)}, ${spbu.longitude.toFixed(4)}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// --- Update Stats ---
|
||||
function updateStats() {
|
||||
document.getElementById('totalSpbu').textContent = spbuData.length;
|
||||
document.getElementById('total24Jam').textContent = spbuData.filter(s => s.status === '24jam').length;
|
||||
}
|
||||
|
||||
// --- Notifications ---
|
||||
function showNotification(message) {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed bottom-4 right-4 bg-teal-600 text-white px-4 py-2 rounded-lg shadow-lg z-[9999] text-sm font-medium fade-in';
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// --- UI Functions ---
|
||||
function renderAuthContainer() {
|
||||
const container = document.getElementById('authContainer');
|
||||
if (isAdminMode) {
|
||||
container.innerHTML = `
|
||||
<div class="flex items-center gap-2 bg-white/10 rounded-full px-3 py-1.5 border border-white/20">
|
||||
<span class="text-xs text-white font-medium">Admin</span>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="adminToggle" class="sr-only peer" checked>
|
||||
<div class="w-9 h-5 bg-teal-500 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all"></div>
|
||||
</label>
|
||||
<button id="btnLogout" class="text-xs text-white hover:text-teal-200 transition-colors ml-1" title="Logout">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<button id="btnShowLogin" class="bg-white/20 hover:bg-white/30 text-white text-sm font-medium px-4 py-2 rounded-full border border-white/30 transition-all flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
Login
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdminUI() {
|
||||
const btnAddSpbu = document.getElementById('btnAddSpbu');
|
||||
const mapHint = document.getElementById('mapHint');
|
||||
const mapContainer = document.getElementById('mapContainer');
|
||||
const modeIndicator = document.getElementById('modeIndicator');
|
||||
|
||||
if (isAdminMode) {
|
||||
btnAddSpbu.style.display = 'flex';
|
||||
mapHint.style.display = 'block';
|
||||
mapContainer.classList.add('admin-mode');
|
||||
} else {
|
||||
btnAddSpbu.style.display = 'none';
|
||||
mapHint.style.display = 'none';
|
||||
mapContainer.classList.remove('admin-mode');
|
||||
isSelectingLocation = false;
|
||||
removeTempMarker();
|
||||
modeIndicator.classList.add('hidden');
|
||||
}
|
||||
|
||||
renderAllMarkers();
|
||||
renderList();
|
||||
updateStats();
|
||||
}
|
||||
|
||||
function zoomToSpbu(id) {
|
||||
const spbu = spbuData.find(s => s.id === id);
|
||||
if (spbu && spbuMarkers[id]) {
|
||||
map.flyTo([spbu.latitude, spbu.longitude], 16, { duration: 1.5 });
|
||||
spbuMarkers[id].openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
function editSpbu(id) {
|
||||
const spbu = spbuData.find(s => s.id === id);
|
||||
if (spbu && isAdminMode) {
|
||||
map.closePopup();
|
||||
const modal = document.getElementById('modalOverlay');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const form = document.getElementById('spbuForm');
|
||||
|
||||
form.reset();
|
||||
document.getElementById('spbuId').value = spbu.id;
|
||||
title.textContent = 'Edit Data SPBU';
|
||||
document.getElementById('inputNama').value = spbu.nama;
|
||||
document.getElementById('inputNo').value = spbu.no_spbu;
|
||||
document.getElementById('inputStatus').value = spbu.status;
|
||||
document.getElementById('inputLat').value = spbu.latitude;
|
||||
document.getElementById('inputLng').value = spbu.longitude;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.add('hidden');
|
||||
if (isSelectingLocation) {
|
||||
isSelectingLocation = false;
|
||||
removeTempMarker();
|
||||
document.getElementById('modeIndicator').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Event Listeners ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMap();
|
||||
|
||||
// Search inputs
|
||||
const handleSearch = (e) => {
|
||||
searchQuery = e.target.value;
|
||||
renderList();
|
||||
};
|
||||
document.getElementById('searchInput').addEventListener('input', handleSearch);
|
||||
document.getElementById('searchInputMobile').addEventListener('input', handleSearch);
|
||||
|
||||
// Auth container events
|
||||
document.getElementById('authContainer').addEventListener('click', (e) => {
|
||||
if (e.target.closest('#btnShowLogin')) {
|
||||
document.getElementById('loginOverlay').classList.remove('hidden');
|
||||
}
|
||||
if (e.target.closest('#btnLogout')) {
|
||||
logout();
|
||||
}
|
||||
if (e.target.closest('#adminToggle')) {
|
||||
logout();
|
||||
}
|
||||
});
|
||||
|
||||
// Login form
|
||||
document.getElementById('loginForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('loginUsername').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
login(username, password);
|
||||
});
|
||||
document.getElementById('btnCancelLogin').addEventListener('click', () => {
|
||||
document.getElementById('loginOverlay').classList.add('hidden');
|
||||
});
|
||||
document.getElementById('loginOverlay').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('loginOverlay')) {
|
||||
document.getElementById('loginOverlay').classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Tombol tambah SPBU
|
||||
document.getElementById('btnAddSpbu').addEventListener('click', () => {
|
||||
if (isAdminMode) {
|
||||
isSelectingLocation = true;
|
||||
document.getElementById('modeIndicator').classList.remove('hidden');
|
||||
showNotification('Silakan klik pada peta untuk memilih lokasi SPBU');
|
||||
}
|
||||
});
|
||||
|
||||
// Modal controls
|
||||
document.getElementById('btnCloseModal').addEventListener('click', closeModal);
|
||||
document.getElementById('btnCancelModal').addEventListener('click', closeModal);
|
||||
document.getElementById('modalOverlay').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('modalOverlay')) closeModal();
|
||||
});
|
||||
|
||||
// Form submit SPBU
|
||||
document.getElementById('spbuForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
if (!isAdminMode) return;
|
||||
|
||||
const id = document.getElementById('spbuId').value;
|
||||
const nama = document.getElementById('inputNama').value;
|
||||
const no_spbu = document.getElementById('inputNo').value;
|
||||
const status = document.getElementById('inputStatus').value;
|
||||
const latitude = parseFloat(document.getElementById('inputLat').value);
|
||||
const longitude = parseFloat(document.getElementById('inputLng').value);
|
||||
|
||||
const data = { nama, no_spbu, status, latitude, longitude };
|
||||
|
||||
if (id) {
|
||||
// Update
|
||||
data.id = parseInt(id);
|
||||
await updateSpbu(data);
|
||||
} else {
|
||||
// Create
|
||||
await addSpbu(data);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
isSelectingLocation = false;
|
||||
removeTempMarker();
|
||||
document.getElementById('modeIndicator').classList.add('hidden');
|
||||
|
||||
// Fly to location
|
||||
map.flyTo([latitude, longitude], 16, { duration: 1 });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebGIS Dashboard - Sistem Informasi Geografis</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-500-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-800-normal.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', sans-serif; overflow-x: hidden; overflow-y: auto; }
|
||||
|
||||
@keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-20px); } }
|
||||
@keyframes fadeInUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-60px); } to { opacity: 1; transform: translateX(0); } }
|
||||
@keyframes slideInRight { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: translateX(0); } }
|
||||
|
||||
.animate-float { animation: float 6s ease-in-out infinite; }
|
||||
.animate-fade-in-up { animation: fadeInUp 0.8s ease-out forwards; }
|
||||
.animate-fade-in { animation: fadeIn 1.2s ease-out forwards; }
|
||||
.animate-gradient { background-size: 200% 200%; animation: gradientShift 8s ease infinite; }
|
||||
.animate-pulse-slow { animation: pulse 3s ease-in-out infinite; }
|
||||
.animate-slide-left { animation: slideInLeft 0.8s ease-out forwards; }
|
||||
.animate-slide-right { animation: slideInRight 0.8s ease-out forwards; }
|
||||
.delay-100 { animation-delay: 0.1s; }
|
||||
.delay-200 { animation-delay: 0.2s; }
|
||||
.delay-300 { animation-delay: 0.3s; }
|
||||
.delay-400 { animation-delay: 0.4s; }
|
||||
.delay-500 { animation-delay: 0.5s; }
|
||||
|
||||
.card-hover { transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
|
||||
.card-hover:hover { transform: translateY(-12px) scale(1.02); }
|
||||
.card-hover:hover .card-glow { opacity: 1; }
|
||||
.card-glow { opacity: 0; transition: opacity 0.4s ease; }
|
||||
|
||||
.glass { background: rgba(255, 255, 255, 0.08); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.12); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen relative" style="background: #0a0a0f;">
|
||||
|
||||
<div class="fixed inset-0 z-0">
|
||||
<div class="absolute inset-0 animate-gradient" style="background: radial-gradient(ellipse at 20% 50%, rgba(20, 184, 166, 0.15) 0%, transparent 50%), radial-gradient(ellipse at 80% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), radial-gradient(ellipse at 50% 80%, rgba(236, 72, 153, 0.1) 0%, transparent 50%), #0a0a0f;"></div>
|
||||
<div class="absolute inset-0" style="background-image: url('data:image/svg+xml,<svg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill="%23ffffff" fill-opacity="0.03"><circle cx="30" cy="30" r="1"/></g></g></svg>');"></div>
|
||||
</div>
|
||||
|
||||
<div class="fixed top-20 left-10 w-72 h-72 rounded-full blur-3xl animate-pulse-slow pointer-events-none" style="background: rgba(20, 184, 166, 0.1); z-index: 0;"></div>
|
||||
<div class="fixed bottom-20 right-10 w-96 h-96 rounded-full blur-3xl animate-pulse-slow pointer-events-none" style="background: rgba(99, 102, 241, 0.1); z-index: 0; animation-delay: 2s;"></div>
|
||||
<div class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 rounded-full blur-3xl animate-pulse-slow pointer-events-none" style="background: rgba(236, 72, 153, 0.06); z-index: 0; animation-delay: 1s;"></div>
|
||||
|
||||
<div class="relative z-10 min-h-screen flex flex-col items-center justify-center px-4 py-12 md:py-16">
|
||||
|
||||
<div class="text-center mb-10 md:mb-16 animate-fade-in-up">
|
||||
<div class="inline-flex items-center gap-3 mb-6 glass rounded-2xl px-5 py-2.5">
|
||||
<svg class="w-5 h-5 text-teal-400" 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>
|
||||
<span class="text-xs font-semibold text-teal-300 tracking-widest uppercase">Sistem Informasi Geografis</span>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl md:text-5xl lg:text-7xl font-extrabold mb-4 tracking-tight">
|
||||
<span class="bg-gradient-to-r from-teal-400 via-indigo-400 to-rose-400 bg-clip-text text-transparent animate-gradient">
|
||||
WebGIS Dashboard
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-base md:text-lg lg:text-xl text-slate-400 max-w-2xl mx-auto animate-fade-in-up delay-100 px-4">
|
||||
Platform terintegrasi untuk mengelola data geospasial dan pelaporan jalan rusak
|
||||
</p>
|
||||
<div class="w-24 h-1 bg-gradient-to-r from-teal-500 to-indigo-500 rounded-full mx-auto mt-6 animate-fade-in-up delay-200"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 w-full max-w-6xl px-4">
|
||||
|
||||
<div class="animate-slide-left delay-300">
|
||||
<a href="SPBU/" class="card-hover block relative group cursor-pointer">
|
||||
<div class="card-glow absolute -inset-1 rounded-3xl opacity-0 transition-opacity duration-400" style="background: linear-gradient(135deg, rgba(20, 184, 166, 0.4), rgba(20, 184, 166, 0.1));"></div>
|
||||
<div class="relative glass rounded-3xl p-6 md:p-8 h-full flex flex-col items-center text-center">
|
||||
<div class="w-16 h-16 md:w-20 md:h-20 rounded-2xl flex items-center justify-center mb-4 md:mb-6" style="background: linear-gradient(135deg, rgba(20, 184, 166, 0.2), rgba(20, 184, 166, 0.05));">
|
||||
<svg class="w-8 h-8 md:w-10 md:h-10 text-teal-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
<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"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg md:text-xl font-bold text-white mb-2 md:mb-3">WebGIS SPBU</h3>
|
||||
<p class="text-slate-400 text-xs md:text-sm mb-4 md:mb-6 leading-relaxed">Manajemen data SPBU dengan visualisasi peta interaktif</p>
|
||||
<div class="mt-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 md:px-5 md:py-2.5 rounded-xl text-xs md:text-sm font-semibold text-white transition-all hover:shadow-lg" style="background: linear-gradient(135deg, #14b8a6, #0d9488);">
|
||||
Buka Aplikasi
|
||||
<svg class="w-3 h-3 md:w-4 md:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="animate-fade-in-up delay-400">
|
||||
<a href="Layer-Group/" class="card-hover block relative group cursor-pointer">
|
||||
<div class="card-glow absolute -inset-1 rounded-3xl opacity-0 transition-opacity duration-400" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.4), rgba(99, 102, 241, 0.1));"></div>
|
||||
<div class="relative glass rounded-3xl p-6 md:p-8 h-full flex flex-col items-center text-center">
|
||||
<div class="w-16 h-16 md:w-20 md:h-20 rounded-2xl flex items-center justify-center mb-4 md:mb-6" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(99, 102, 241, 0.05));">
|
||||
<svg class="w-8 h-8 md:w-10 md:h-10 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg md:text-xl font-bold text-white mb-2 md:mb-3">Layer Control</h3>
|
||||
<p class="text-slate-400 text-xs md:text-sm mb-4 md:mb-6 leading-relaxed">Kontrol layer peta lengkap dengan berbagai tipe basemap, toggle SPBU 24 jam & terbatas</p>
|
||||
<div class="mt-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 md:px-5 md:py-2.5 rounded-xl text-xs md:text-sm font-semibold text-white transition-all hover:shadow-lg" style="background: linear-gradient(135deg, #6366f1, #4f46e5);">
|
||||
Buka Aplikasi
|
||||
<svg class="w-3 h-3 md:w-4 md:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="animate-slide-right delay-500 md:col-span-2 lg:col-span-1">
|
||||
<a href="Jalan-Rusak/" class="card-hover block relative group cursor-pointer">
|
||||
<div class="card-glow absolute -inset-1 rounded-3xl opacity-0 transition-opacity duration-400" style="background: linear-gradient(135deg, rgba(236, 72, 153, 0.4), rgba(236, 72, 153, 0.1));"></div>
|
||||
<div class="relative glass rounded-3xl p-6 md:p-8 h-full flex flex-col items-center text-center">
|
||||
<div class="w-16 h-16 md:w-20 md:h-20 rounded-2xl flex items-center justify-center mb-4 md:mb-6" style="background: linear-gradient(135deg, rgba(236, 72, 153, 0.2), rgba(236, 72, 153, 0.05));">
|
||||
<svg class="w-8 h-8 md:w-10 md:h-10 text-rose-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2L2 7l10 5 10-5-10-5z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2 17l10 5 10-5"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg md:text-xl font-bold text-white mb-2 md:mb-3">Jalan Rusak</h3>
|
||||
<p class="text-slate-400 text-xs md:text-sm mb-4 md:mb-6 leading-relaxed">Manajemen data SPBU, jalan, dan parsil tanah serta pelaporan jalan rusak</p>
|
||||
<div class="mt-auto">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 md:px-5 md:py-2.5 rounded-xl text-xs md:text-sm font-semibold text-white transition-all hover:shadow-lg" style="background: linear-gradient(135deg, #ec4899, #be185d);">
|
||||
Buka Aplikasi
|
||||
<svg class="w-3 h-3 md:w-4 md:h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 md:mt-16 text-center animate-fade-in-up delay-500 px-4">
|
||||
<p class="text-[10px] md:text-xs text-slate-600 mt-4">© 2026 WebGIS Dashboard - Sistem Informasi Geografis Terintegrasi</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user