First commit
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// API DATA SPBU (Point / Marker)
|
||||
// File: api_spbu.php
|
||||
// Fields: nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude
|
||||
// ============================================================
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
try {
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$rawInput = file_get_contents('php://input');
|
||||
$jsonInput = $rawInput ? json_decode($rawInput, true) : null;
|
||||
if (!is_array($jsonInput)) {
|
||||
$jsonInput = [];
|
||||
}
|
||||
|
||||
// Fallback jika server memblokir method DELETE/PUT
|
||||
if ($method === 'POST' && isset($_GET['_method'])) {
|
||||
$method = strtoupper($_GET['_method']);
|
||||
} elseif ($method === 'POST' && !empty($jsonInput['_method'])) {
|
||||
$method = strtoupper($jsonInput['_method']);
|
||||
}
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
if (!$id && !empty($jsonInput['id'])) {
|
||||
$id = (int)$jsonInput['id'];
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$db = getDB();
|
||||
ensureSpbuSchema($db);
|
||||
|
||||
if ($id) {
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id, nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude
|
||||
FROM data_spbu WHERE id = ?"
|
||||
);
|
||||
$stmt->bind_param('i', $id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result()->fetch_assoc();
|
||||
if ($result) {
|
||||
$result['latitude'] = (float)$result['latitude'];
|
||||
$result['longitude'] = (float)$result['longitude'];
|
||||
echo json_encode($result);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Data SPBU tidak ditemukan']);
|
||||
}
|
||||
} else {
|
||||
$result = $db->query(
|
||||
"SELECT id, nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude
|
||||
FROM data_spbu ORDER BY id ASC"
|
||||
);
|
||||
if (!$result) {
|
||||
jsonError('Query gagal: ' . $db->error);
|
||||
}
|
||||
$rows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$row['latitude'] = (float)$row['latitude'];
|
||||
$row['longitude'] = (float)$row['longitude'];
|
||||
$rows[] = $row;
|
||||
}
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$db->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($method === 'POST') {
|
||||
$input = $jsonInput;
|
||||
|
||||
if (empty($input['nama_spbu']) || empty($input['alamat']) || empty($input['no_wa'])
|
||||
|| !isset($input['latitude']) || !isset($input['longitude'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Data tidak lengkap. Field wajib: nama_spbu, alamat, no_wa, latitude, longitude']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$nama_spbu = trim($input['nama_spbu']);
|
||||
$alamat = trim($input['alamat']);
|
||||
$no_wa = trim($input['no_wa']);
|
||||
$buka_24_jam = isset($input['buka_24_jam']) && $input['buka_24_jam'] === 'Ya' ? 'Ya' : 'Tidak';
|
||||
$latitude = (float)$input['latitude'];
|
||||
$longitude = (float)$input['longitude'];
|
||||
|
||||
$db = getDB();
|
||||
ensureSpbuSchema($db);
|
||||
$stmt = $db->prepare(
|
||||
"INSERT INTO data_spbu (nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude)
|
||||
VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->bind_param('ssssdd', $nama_spbu, $alamat, $no_wa, $buka_24_jam, $latitude, $longitude);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
http_response_code(201);
|
||||
echo json_encode([
|
||||
'message' => 'Data SPBU berhasil disimpan',
|
||||
'id' => $db->insert_id,
|
||||
'nama_spbu' => $nama_spbu,
|
||||
'alamat' => $alamat,
|
||||
'no_wa' => $no_wa,
|
||||
'buka_24_jam' => $buka_24_jam,
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Gagal menyimpan data: ' . $stmt->error]);
|
||||
}
|
||||
$db->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($method === 'PUT') {
|
||||
if (!$id) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'ID diperlukan untuk update']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$input = $jsonInput;
|
||||
$db = getDB();
|
||||
ensureSpbuSchema($db);
|
||||
|
||||
if (isset($input['latitude']) && isset($input['longitude'])
|
||||
&& !isset($input['nama_spbu'])) {
|
||||
$latitude = (float)$input['latitude'];
|
||||
$longitude = (float)$input['longitude'];
|
||||
$stmt = $db->prepare("UPDATE data_spbu SET latitude = ?, longitude = ? WHERE id = ?");
|
||||
$stmt->bind_param('ddi', $latitude, $longitude, $id);
|
||||
} else {
|
||||
$nama_spbu = trim($input['nama_spbu'] ?? '');
|
||||
$alamat = trim($input['alamat'] ?? '');
|
||||
$no_wa = trim($input['no_wa'] ?? '');
|
||||
$buka_24_jam = isset($input['buka_24_jam']) && $input['buka_24_jam'] === 'Ya' ? 'Ya' : 'Tidak';
|
||||
$latitude = (float)($input['latitude'] ?? 0);
|
||||
$longitude = (float)($input['longitude'] ?? 0);
|
||||
|
||||
if (empty($nama_spbu) || empty($alamat) || empty($no_wa)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'nama_spbu, alamat, dan no_wa wajib diisi']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$stmt = $db->prepare(
|
||||
"UPDATE data_spbu
|
||||
SET nama_spbu = ?, alamat = ?, no_wa = ?, buka_24_jam = ?, latitude = ?, longitude = ?
|
||||
WHERE id = ?"
|
||||
);
|
||||
$stmt->bind_param('ssssddi', $nama_spbu, $alamat, $no_wa, $buka_24_jam, $latitude, $longitude, $id);
|
||||
}
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['message' => 'Data SPBU berhasil diperbarui', 'id' => $id]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Gagal memperbarui data: ' . $stmt->error]);
|
||||
}
|
||||
$db->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($method === 'DELETE') {
|
||||
if (!$id) {
|
||||
jsonError('ID diperlukan untuk hapus', 400);
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
ensureSpbuSchema($db);
|
||||
$stmt = $db->prepare("DELETE FROM data_spbu WHERE id = ?");
|
||||
if (!$stmt) {
|
||||
jsonError('Gagal menyiapkan query hapus: ' . $db->error);
|
||||
}
|
||||
|
||||
$stmt->bind_param('i', $id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
if ($stmt->affected_rows > 0) {
|
||||
echo json_encode(['message' => 'Data SPBU berhasil dihapus', 'id' => $id], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
jsonError('Data SPBU tidak ditemukan', 404);
|
||||
}
|
||||
} else {
|
||||
jsonError('Gagal menghapus data: ' . $stmt->error);
|
||||
}
|
||||
$stmt->close();
|
||||
$db->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
jsonError('Method tidak diizinkan', 405);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
jsonError('Server error: ' . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// KONFIGURASI DATABASE – WebGIS SPBU
|
||||
// File: config.php
|
||||
// ============================================================
|
||||
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_USER', 'root');
|
||||
define('DB_PASS', '');
|
||||
define('DB_NAME', 'webgis_spbu');
|
||||
|
||||
function jsonError($message, $code = 500) {
|
||||
http_response_code($code);
|
||||
echo json_encode(['error' => $message]);
|
||||
exit();
|
||||
}
|
||||
|
||||
function getDB() {
|
||||
mysqli_report(MYSQLI_REPORT_OFF);
|
||||
|
||||
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS);
|
||||
if ($conn->connect_error) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['error' => 'Koneksi MySQL gagal: ' . $conn->connect_error]));
|
||||
}
|
||||
|
||||
$dbName = DB_NAME;
|
||||
if (!$conn->query("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['error' => 'Gagal membuat database: ' . $conn->error]));
|
||||
}
|
||||
|
||||
if (!$conn->select_db($dbName)) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['error' => 'Gagal memilih database: ' . $conn->error]));
|
||||
}
|
||||
|
||||
$conn->set_charset('utf8mb4');
|
||||
return $conn;
|
||||
}
|
||||
|
||||
function ensureSpbuSchema($db) {
|
||||
if (!$db->query("
|
||||
CREATE TABLE IF NOT EXISTS data_spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_spbu VARCHAR(200) NOT NULL,
|
||||
alamat VARCHAR(300) NOT NULL DEFAULT '',
|
||||
no_wa VARCHAR(20) NOT NULL,
|
||||
buka_24_jam ENUM('Ya','Tidak') NOT NULL DEFAULT 'Tidak',
|
||||
latitude DOUBLE NOT NULL,
|
||||
longitude DOUBLE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
")) {
|
||||
jsonError('Gagal membuat tabel data_spbu: ' . $db->error);
|
||||
}
|
||||
|
||||
$check = $db->query("SHOW COLUMNS FROM data_spbu LIKE 'alamat'");
|
||||
if ($check && $check->num_rows === 0) {
|
||||
$db->query("ALTER TABLE data_spbu ADD COLUMN alamat VARCHAR(300) NOT NULL DEFAULT '' AFTER nama_spbu");
|
||||
$db->query("UPDATE data_spbu SET alamat = CONCAT(nama_spbu, ', Pontianak') WHERE alamat = ''");
|
||||
}
|
||||
|
||||
$countRes = $db->query("SELECT COUNT(*) AS total FROM data_spbu");
|
||||
if ($countRes && (int)$countRes->fetch_assoc()['total'] === 0) {
|
||||
seedSpbuSampleData($db);
|
||||
}
|
||||
}
|
||||
|
||||
function seedSpbuSampleData($db) {
|
||||
$samples = [
|
||||
['SPBU Jl. Ahmad Yani', 'Jl. Ahmad Yani No. 12, Pontianak', '0812-1111-0001', 'Ya', -0.0400, 109.3200],
|
||||
['SPBU Jl. Gajah Mada', 'Jl. Gajah Mada No. 45, Pontianak', '0812-1111-0002', 'Ya', -0.0520, 109.3450],
|
||||
['SPBU Jl. Diponegoro', 'Jl. Diponegoro No. 88, Pontianak', '0812-1111-0003', 'Tidak', -0.0610, 109.3600],
|
||||
['SPBU Jl. Sutan Syahrir', 'Jl. Sutan Syahrir No. 5, Pontianak', '0812-1111-0004', 'Tidak', -0.0480, 109.3700],
|
||||
['SPBU Jl. Kom. Yos Sudarso', 'Jl. Kom. Yos Sudarso No. 22, Pontianak', '0812-1111-0005', 'Ya', -0.0350, 109.3550],
|
||||
['SPBU Jl. Tanjung Raya', 'Jl. Tanjung Raya No. 101, Pontianak', '0812-1111-0006', 'Tidak', -0.0580, 109.3300],
|
||||
['SPBU Jl. Veteran', 'Jl. Veteran No. 33, Pontianak', '0812-1111-0007', 'Ya', -0.0450, 109.3380],
|
||||
];
|
||||
|
||||
$stmt = $db->prepare(
|
||||
"INSERT INTO data_spbu (nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude)
|
||||
VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
foreach ($samples as $row) {
|
||||
[$nama, $alamat, $wa, $jam, $lat, $lng] = $row;
|
||||
$stmt->bind_param('ssssdd', $nama, $alamat, $wa, $jam, $lat, $lng);
|
||||
$stmt->execute();
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
-- ============================================================
|
||||
-- DATABASE: webgis_spbu
|
||||
-- Pertemuan 3 – Layer Groups and Layers Control
|
||||
-- Data SPBU dengan 2 Layer Group: Buka 24 Jam & Tidak 24 Jam
|
||||
-- ============================================================
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS webgis_spbu
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE webgis_spbu;
|
||||
|
||||
-- buka_24_jam: 'Ya' → Layer Group "SPBU Buka 24 Jam"
|
||||
-- 'Tidak' → Layer Group "SPBU Tidak Buka 24 Jam"
|
||||
CREATE TABLE IF NOT EXISTS data_spbu (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_spbu VARCHAR(200) NOT NULL,
|
||||
alamat VARCHAR(300) NOT NULL,
|
||||
no_wa VARCHAR(20) NOT NULL,
|
||||
buka_24_jam ENUM('Ya','Tidak') NOT NULL DEFAULT 'Tidak',
|
||||
latitude DOUBLE NOT NULL,
|
||||
longitude DOUBLE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Migrasi: tambah kolom alamat jika tabel lama dari PovertyMap belum punya
|
||||
-- ALTER TABLE data_spbu ADD COLUMN alamat VARCHAR(300) NOT NULL DEFAULT '' AFTER nama_spbu;
|
||||
-- UPDATE data_spbu SET alamat = CONCAT(nama_spbu, ', Pontianak') WHERE alamat = '';
|
||||
|
||||
INSERT IGNORE INTO data_spbu (nama_spbu, alamat, no_wa, buka_24_jam, latitude, longitude) VALUES
|
||||
('SPBU Jl. Ahmad Yani', 'Jl. Ahmad Yani No. 12, Pontianak', '0812-1111-0001', 'Ya', -0.0400, 109.3200),
|
||||
('SPBU Jl. Gajah Mada', 'Jl. Gajah Mada No. 45, Pontianak', '0812-1111-0002', 'Ya', -0.0520, 109.3450),
|
||||
('SPBU Jl. Diponegoro', 'Jl. Diponegoro No. 88, Pontianak', '0812-1111-0003', 'Tidak', -0.0610, 109.3600),
|
||||
('SPBU Jl. Sutan Syahrir', 'Jl. Sutan Syahrir No. 5, Pontianak', '0812-1111-0004', 'Tidak', -0.0480, 109.3700),
|
||||
('SPBU Jl. Kom. Yos Sudarso', 'Jl. Kom. Yos Sudarso No. 22, Pontianak', '0812-1111-0005', 'Ya', -0.0350, 109.3550),
|
||||
('SPBU Jl. Tanjung Raya', 'Jl. Tanjung Raya No. 101, Pontianak', '0812-1111-0006', 'Tidak', -0.0580, 109.3300),
|
||||
('SPBU Jl. Veteran', 'Jl. Veteran No. 33, Pontianak', '0812-1111-0007', 'Ya', -0.0450, 109.3380);
|
||||
@@ -0,0 +1,562 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="WebGIS SPBU – Layer Groups & Layers Control">
|
||||
<title>WebGIS SPBU – Layer Groups</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header id="header-bar">
|
||||
<div>
|
||||
<h1>⛽ WebGIS SPBU Pontianak</h1>
|
||||
<p>Layer Groups & Layers Control – Pertemuan 2</p>
|
||||
</div>
|
||||
<div id="stats-bar">
|
||||
<span class="stat-badge green" id="count-24jam">🟢 24 Jam: 0</span>
|
||||
<span class="stat-badge orange" id="count-tidak24jam">🔴 Tidak 24 Jam: 0</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="fab-container">
|
||||
<button class="fab-btn" id="fab-tambah" title="Tambah SPBU" onclick="aktifkanModeTambah()">➕</button>
|
||||
</div>
|
||||
|
||||
<div id="toast"></div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<script>
|
||||
// Gunakan path relatif sederhana — lebih stabil dari URL constructor
|
||||
const API_SPBU = 'api_spbu.php';
|
||||
|
||||
// ============================================================
|
||||
// INISIALISASI PETA (mengikuti pola Leaflet Layers Control)
|
||||
// https://leafletjs.com/examples/layers-control/
|
||||
// ============================================================
|
||||
const map = L.map('map').setView([-0.0553, 109.3495], 13);
|
||||
|
||||
const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19
|
||||
});
|
||||
|
||||
const satelliteLayer = L.tileLayer(
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
{ attribution: 'Tiles © Esri', maxZoom: 19 }
|
||||
);
|
||||
|
||||
osmLayer.addTo(map);
|
||||
|
||||
// ============================================================
|
||||
// LAYER GROUPS – 2 grup SPBU
|
||||
// ============================================================
|
||||
const layer24Jam = L.layerGroup();
|
||||
const layerTidak24Jam = L.layerGroup();
|
||||
|
||||
// Kedua layer tampil secara default saat app dimuat
|
||||
layer24Jam.addTo(map);
|
||||
layerTidak24Jam.addTo(map);
|
||||
|
||||
// ============================================================
|
||||
// BASE MAPS & OVERLAY MAPS
|
||||
// ============================================================
|
||||
const baseMaps = {
|
||||
'🗺️ OpenStreetMap': osmLayer,
|
||||
'🛰️ Citra Satelit': satelliteLayer
|
||||
};
|
||||
|
||||
const overlayMaps = {
|
||||
'🟢 SPBU Buka 24 Jam': layer24Jam,
|
||||
'🔴 SPBU Tidak 24 Jam': layerTidak24Jam
|
||||
};
|
||||
|
||||
L.control.layers(baseMaps, overlayMaps, {
|
||||
collapsed: false,
|
||||
position: 'topright'
|
||||
}).addTo(map);
|
||||
|
||||
// ============================================================
|
||||
// IKON MARKER – warna berbeda per grup
|
||||
// ============================================================
|
||||
function ikonSpbu(buka24Jam) {
|
||||
const is24Jam = buka24Jam === 'Ya';
|
||||
const color = is24Jam ? '#10b981' : '#f97316';
|
||||
const border = is24Jam ? '#065f46' : '#9a3412';
|
||||
const emoji = '⛽';
|
||||
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="width:32px;height:32px;border-radius:50% 50% 50% 0;background:${color};transform:rotate(-45deg);border:3px solid ${border};box-shadow:0 3px 10px rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;">
|
||||
<span style="transform:rotate(45deg);font-size:14px;line-height:1">${emoji}</span>
|
||||
</div>`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [0, -34]
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POPUP INFO
|
||||
// ============================================================
|
||||
function statusLabel(buka24Jam) {
|
||||
return buka24Jam === 'Ya' ? 'Buka 24 Jam' : 'Tidak Buka 24 Jam';
|
||||
}
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function popupInfoSpbu(d) {
|
||||
const is24Jam = d.buka_24_jam === 'Ya';
|
||||
const statusClass = is24Jam ? 'status-24jam' : 'status-tidak24jam';
|
||||
const statusText = statusLabel(d.buka_24_jam);
|
||||
const titleColor = is24Jam ? '#10b981' : '#f97316';
|
||||
|
||||
return `<div class="gis-popup">
|
||||
<div class="popup-title" style="border-bottom-color:${titleColor}">⛽ ${escHtml(d.nama_spbu)}</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Alamat</span>
|
||||
<span class="info-value" style="font-size:11px">${escHtml(d.alamat)}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Status</span>
|
||||
<span class="info-value"><span class="status-badge ${statusClass}">${statusText}</span></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">WhatsApp</span>
|
||||
<span class="info-value">${escHtml(d.no_wa)}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Koordinat</span>
|
||||
<span class="info-value" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span>
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button type="button" class="popup-btn popup-btn-edit btn-edit-spbu" data-id="${d.id}" onclick="event.stopPropagation(); bukaFormEditSpbu(${d.id}); return false;">✏️ Edit</button>
|
||||
<button type="button" class="popup-btn popup-btn-delete btn-delete-spbu" data-id="${d.id}" onclick="event.stopPropagation(); konfirmasiHapusSpbu(${d.id}); return false;">🗑️ Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function formSpbuHtml({ title, prefix, lat, lng, d = {} }) {
|
||||
const buka24 = d.buka_24_jam === 'Ya' ? 'Ya' : 'Tidak';
|
||||
return `<div class="gis-popup">
|
||||
<div class="popup-title">${title}</div>
|
||||
<div class="popup-form">
|
||||
<label>Nama SPBU</label>
|
||||
<input type="text" id="${prefix}-nama" placeholder="Contoh: SPBU Jl. Ahmad Yani" value="${escHtml(d.nama_spbu || '')}">
|
||||
<label>Alamat</label>
|
||||
<input type="text" id="${prefix}-alamat" placeholder="Alamat lengkap" value="${escHtml(d.alamat || '')}">
|
||||
<label>WhatsApp</label>
|
||||
<input type="text" id="${prefix}-wa" placeholder="08xx-xxxx-xxxx" value="${escHtml(d.no_wa || '')}">
|
||||
<label>Status Operasional</label>
|
||||
<select id="${prefix}-24jam">
|
||||
<option value="Ya"${buka24 === 'Ya' ? ' selected' : ''}>🟢 Buka 24 Jam</option>
|
||||
<option value="Tidak"${buka24 === 'Tidak' ? ' selected' : ''}>🔴 Tidak Buka 24 Jam</option>
|
||||
</select>
|
||||
<label>Koordinat</label>
|
||||
<input type="text" id="${prefix}-koord" readonly value="${lat.toFixed(6)}, ${lng.toFixed(6)}" style="font-size:10px">
|
||||
</div>
|
||||
<div class="popup-actions">
|
||||
<button class="popup-btn popup-btn-save" onclick="${prefix === 'add' ? `simpanSpbu(${lat},${lng})` : `updateSpbu(${d.id})`}">💾 Simpan</button>
|
||||
<button class="popup-btn popup-btn-cancel" onclick="map.closePopup()">✖ Batal</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RENDER MARKER KE LAYER GROUP YANG SESUAI
|
||||
// ============================================================
|
||||
const spbuMarkers = {};
|
||||
const spbuDataCache = {};
|
||||
let modeTambah = false;
|
||||
let dbReady = false;
|
||||
|
||||
function getAllSpbuData() {
|
||||
return Object.values(spbuDataCache);
|
||||
}
|
||||
|
||||
function normalizeId(id) {
|
||||
return Number(id);
|
||||
}
|
||||
|
||||
function parseJsonResponse(text) {
|
||||
const clean = (text || '').replace(/^\uFEFF/, '').trim();
|
||||
if (!clean) return null;
|
||||
try {
|
||||
return JSON.parse(clean);
|
||||
} catch (e) {
|
||||
throw new Error('Respons server bukan JSON valid');
|
||||
}
|
||||
}
|
||||
|
||||
async function apiSend(method, id, body) {
|
||||
const hasId = id !== null && id !== undefined && id !== '';
|
||||
const baseUrl = hasId ? `${API_SPBU}?id=${normalizeId(id)}` : API_SPBU;
|
||||
const fetchOpts = {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
if (body !== undefined) {
|
||||
fetchOpts.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
let res = await fetch(baseUrl, fetchOpts);
|
||||
|
||||
if ((method === 'DELETE' || method === 'PUT') && (res.status === 405 || res.status === 501)) {
|
||||
res = await fetch(`${baseUrl}&_method=${method}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...(body || {}), _method: method, id: normalizeId(id) })
|
||||
});
|
||||
}
|
||||
|
||||
const data = parseJsonResponse(await res.text());
|
||||
if (!res.ok) {
|
||||
throw new Error((data && data.error) ? data.error : `Server error (${res.status})`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function apiGetAll() {
|
||||
const res = await fetch(API_SPBU);
|
||||
const data = parseJsonResponse(await res.text());
|
||||
if (!res.ok) {
|
||||
throw new Error((data && data.error) ? data.error : `Server error (${res.status})`);
|
||||
}
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Data SPBU harus berupa array JSON');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function renderSpbu(d) {
|
||||
const id = normalizeId(d.id);
|
||||
d.id = id;
|
||||
spbuDataCache[id] = d;
|
||||
const targetLayer = d.buka_24_jam === 'Ya' ? layer24Jam : layerTidak24Jam;
|
||||
|
||||
if (spbuMarkers[id]) {
|
||||
const oldMarker = spbuMarkers[id];
|
||||
layer24Jam.removeLayer(oldMarker);
|
||||
layerTidak24Jam.removeLayer(oldMarker);
|
||||
}
|
||||
|
||||
const marker = L.marker([d.latitude, d.longitude], {
|
||||
icon: ikonSpbu(d.buka_24_jam),
|
||||
draggable: true
|
||||
});
|
||||
|
||||
marker._data = d;
|
||||
marker._targetLayer = targetLayer;
|
||||
marker._spbuId = id;
|
||||
|
||||
marker.bindPopup(() => popupInfoSpbu(spbuDataCache[id] || d));
|
||||
marker.on('click', () => marker.openPopup());
|
||||
marker.on('dragend', function () {
|
||||
const pos = marker.getLatLng();
|
||||
const data = spbuDataCache[id];
|
||||
if (!data) return;
|
||||
|
||||
data.latitude = pos.lat;
|
||||
data.longitude = pos.lng;
|
||||
spbuDataCache[id] = data;
|
||||
|
||||
if (!dbReady) {
|
||||
showToast('📍 Posisi diperbarui (mode offline)', 'info');
|
||||
marker.getPopup().setContent(popupInfoSpbu(data));
|
||||
return;
|
||||
}
|
||||
|
||||
apiSend('PUT', id, { latitude: pos.lat, longitude: pos.lng })
|
||||
.then(() => {
|
||||
showToast('📍 Posisi SPBU diperbarui', 'success');
|
||||
marker.getPopup().setContent(popupInfoSpbu(data));
|
||||
})
|
||||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||||
});
|
||||
|
||||
targetLayer.addLayer(marker);
|
||||
spbuMarkers[id] = marker;
|
||||
updateCounts(getAllSpbuData());
|
||||
}
|
||||
|
||||
function removeSpbuFromMap(id) {
|
||||
const numId = normalizeId(id);
|
||||
const marker = spbuMarkers[numId];
|
||||
if (marker) {
|
||||
layer24Jam.removeLayer(marker);
|
||||
layerTidak24Jam.removeLayer(marker);
|
||||
marker.remove();
|
||||
delete spbuMarkers[numId];
|
||||
}
|
||||
delete spbuDataCache[numId];
|
||||
updateCounts(getAllSpbuData());
|
||||
}
|
||||
|
||||
function updateCounts(data) {
|
||||
const count24 = data.filter(d => d.buka_24_jam === 'Ya').length;
|
||||
const countTidak = data.filter(d => d.buka_24_jam === 'Tidak').length;
|
||||
document.getElementById('count-24jam').textContent = `🟢 24 Jam: ${count24}`;
|
||||
document.getElementById('count-tidak24jam').textContent = `🔴 Tidak 24 Jam: ${countTidak}`;
|
||||
}
|
||||
|
||||
function showToast(msg, type = 'info') {
|
||||
const el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'show' + (type ? ' ' + type : '');
|
||||
setTimeout(() => { el.className = ''; }, 3000);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MODE TAMBAH SPBU (FAB + klik peta)
|
||||
// ============================================================
|
||||
function aktifkanModeTambah() {
|
||||
modeTambah = !modeTambah;
|
||||
const fab = document.getElementById('fab-tambah');
|
||||
if (modeTambah) {
|
||||
fab.classList.add('active');
|
||||
fab.textContent = '✖';
|
||||
showToast('📍 Klik lokasi di peta untuk menambah SPBU baru', 'info');
|
||||
} else {
|
||||
fab.classList.remove('active');
|
||||
fab.textContent = '➕';
|
||||
}
|
||||
}
|
||||
window.aktifkanModeTambah = aktifkanModeTambah;
|
||||
|
||||
map.on('click', function (e) {
|
||||
if (!modeTambah) return;
|
||||
modeTambah = false;
|
||||
document.getElementById('fab-tambah').classList.remove('active');
|
||||
document.getElementById('fab-tambah').textContent = '➕';
|
||||
tampilkanFormTambahSpbu(e.latlng.lat, e.latlng.lng);
|
||||
});
|
||||
|
||||
function tampilkanFormTambahSpbu(lat, lng) {
|
||||
L.popup({ closeButton: true, maxWidth: 300 })
|
||||
.setLatLng([lat, lng])
|
||||
.setContent(formSpbuHtml({ title: '➕ Tambah SPBU Baru', prefix: 'add', lat, lng }))
|
||||
.openOn(map);
|
||||
}
|
||||
|
||||
function simpanSpbu(lat, lng) {
|
||||
const nama = document.getElementById('add-nama').value.trim();
|
||||
const alamat = document.getElementById('add-alamat').value.trim();
|
||||
const no_wa = document.getElementById('add-wa').value.trim();
|
||||
const buka_24_jam = document.getElementById('add-24jam').value;
|
||||
|
||||
if (!nama || !alamat || !no_wa) {
|
||||
showToast('⚠️ Nama, alamat, dan WhatsApp wajib diisi!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dbReady) {
|
||||
showToast('❌ Database belum tersedia. Import database.sql terlebih dahulu.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
apiSend('POST', null, { nama_spbu: nama, alamat, no_wa, buka_24_jam, latitude: lat, longitude: lng })
|
||||
.then(res => {
|
||||
map.closePopup();
|
||||
renderSpbu({
|
||||
id: normalizeId(res.id),
|
||||
nama_spbu: res.nama_spbu,
|
||||
alamat: res.alamat,
|
||||
no_wa: res.no_wa,
|
||||
buka_24_jam: res.buka_24_jam,
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude
|
||||
});
|
||||
showToast('✅ SPBU berhasil ditambahkan', 'success');
|
||||
})
|
||||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||||
}
|
||||
window.simpanSpbu = simpanSpbu;
|
||||
|
||||
function bukaFormEditSpbu(id) {
|
||||
const d = spbuDataCache[normalizeId(id)];
|
||||
if (!d) return;
|
||||
|
||||
map.closePopup();
|
||||
L.popup({ closeButton: true, maxWidth: 300 })
|
||||
.setLatLng([d.latitude, d.longitude])
|
||||
.setContent(formSpbuHtml({
|
||||
title: '✏️ Edit SPBU',
|
||||
prefix: 'edit',
|
||||
lat: d.latitude,
|
||||
lng: d.longitude,
|
||||
d
|
||||
}))
|
||||
.openOn(map);
|
||||
}
|
||||
window.bukaFormEditSpbu = bukaFormEditSpbu;
|
||||
|
||||
function updateSpbu(id) {
|
||||
const numId = normalizeId(id);
|
||||
const nama = document.getElementById('edit-nama').value.trim();
|
||||
const alamat = document.getElementById('edit-alamat').value.trim();
|
||||
const no_wa = document.getElementById('edit-wa').value.trim();
|
||||
const buka_24_jam = document.getElementById('edit-24jam').value;
|
||||
const old = spbuDataCache[numId];
|
||||
if (!old) return;
|
||||
|
||||
if (!nama || !alamat || !no_wa) {
|
||||
showToast('⚠️ Nama, alamat, dan WhatsApp wajib diisi!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
nama_spbu: nama,
|
||||
alamat,
|
||||
no_wa,
|
||||
buka_24_jam,
|
||||
latitude: old.latitude,
|
||||
longitude: old.longitude
|
||||
};
|
||||
|
||||
if (!dbReady) {
|
||||
map.closePopup();
|
||||
renderSpbu({ ...payload, id: numId });
|
||||
showToast('✅ Data diperbarui (mode offline)', 'success');
|
||||
return;
|
||||
}
|
||||
|
||||
apiSend('PUT', numId, payload)
|
||||
.then(() => {
|
||||
map.closePopup();
|
||||
renderSpbu({ ...payload, id: numId });
|
||||
showToast('✅ SPBU berhasil diperbarui', 'success');
|
||||
})
|
||||
.catch(err => showToast('❌ ' + err.message, 'error'));
|
||||
}
|
||||
window.updateSpbu = updateSpbu;
|
||||
|
||||
// Tampilkan konfirmasi inline di dalam popup (confirm() diblokir di Leaflet popup)
|
||||
function konfirmasiHapusSpbu(id) {
|
||||
const numId = normalizeId(id);
|
||||
const d = spbuDataCache[numId];
|
||||
if (!d) { showToast('❌ Data SPBU tidak ditemukan', 'error'); return; }
|
||||
|
||||
const nama = d.nama_spbu;
|
||||
const konfirmHtml = `
|
||||
<div class="gis-popup">
|
||||
<div class="popup-title" style="color:#ef4444">🗑️ Hapus SPBU?</div>
|
||||
<p style="margin:8px 0 16px;font-size:12px;color:#94a3b8">"${escHtml(nama)}" akan dihapus permanen.</p>
|
||||
<div class="popup-actions">
|
||||
<button class="popup-btn" onclick="map.closePopup()" style="background:rgba(255,255,255,0.08);color:#94a3b8">Batal</button>
|
||||
<button class="popup-btn popup-btn-delete" onclick="hapusSpbu(${numId})">🗑️ Ya, Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const lyr = spbuMarkers[numId];
|
||||
if (lyr) {
|
||||
lyr.getPopup().setContent(konfirmHtml);
|
||||
} else {
|
||||
// fallback: langsung hapus tanpa konfirmasi popup
|
||||
hapusSpbu(numId);
|
||||
}
|
||||
}
|
||||
window.konfirmasiHapusSpbu = konfirmasiHapusSpbu;
|
||||
|
||||
async function hapusSpbu(id) {
|
||||
const numId = normalizeId(id);
|
||||
const d = spbuDataCache[numId];
|
||||
if (!d) {
|
||||
showToast('❌ Data SPBU tidak ditemukan', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dbReady) {
|
||||
map.closePopup();
|
||||
removeSpbuFromMap(numId);
|
||||
showToast('🗑️ Data dihapus (mode offline)', 'success');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiSend('DELETE', numId);
|
||||
map.closePopup();
|
||||
removeSpbuFromMap(numId);
|
||||
showToast('🗑️ SPBU berhasil dihapus', 'success');
|
||||
} catch (err) {
|
||||
showToast('❌ ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
window.hapusSpbu = hapusSpbu;
|
||||
|
||||
// Data fallback jika database belum diimport
|
||||
const SAMPLE_DATA = [
|
||||
{ id: 1, nama_spbu: 'SPBU Jl. Ahmad Yani', alamat: 'Jl. Ahmad Yani No. 12, Pontianak', no_wa: '0812-1111-0001', buka_24_jam: 'Ya', latitude: -0.0400, longitude: 109.3200 },
|
||||
{ id: 2, nama_spbu: 'SPBU Jl. Gajah Mada', alamat: 'Jl. Gajah Mada No. 45, Pontianak', no_wa: '0812-1111-0002', buka_24_jam: 'Ya', latitude: -0.0520, longitude: 109.3450 },
|
||||
{ id: 3, nama_spbu: 'SPBU Jl. Diponegoro', alamat: 'Jl. Diponegoro No. 88, Pontianak', no_wa: '0812-1111-0003', buka_24_jam: 'Tidak', latitude: -0.0610, longitude: 109.3600 },
|
||||
{ id: 4, nama_spbu: 'SPBU Jl. Sutan Syahrir', alamat: 'Jl. Sutan Syahrir No. 5, Pontianak', no_wa: '0812-1111-0004', buka_24_jam: 'Tidak', latitude: -0.0480, longitude: 109.3700 },
|
||||
{ id: 5, nama_spbu: 'SPBU Jl. Kom. Yos Sudarso', alamat: 'Jl. Kom. Yos Sudarso No. 22, Pontianak', no_wa: '0812-1111-0005', buka_24_jam: 'Ya', latitude: -0.0350, longitude: 109.3550 }
|
||||
];
|
||||
|
||||
function loadSpbuData(data, fitBounds = true) {
|
||||
layer24Jam.clearLayers();
|
||||
layerTidak24Jam.clearLayers();
|
||||
Object.keys(spbuMarkers).forEach(k => delete spbuMarkers[k]);
|
||||
Object.keys(spbuDataCache).forEach(k => delete spbuDataCache[k]);
|
||||
|
||||
data.forEach(d => renderSpbu(d));
|
||||
|
||||
if (fitBounds && data.length > 0) {
|
||||
const bounds = L.latLngBounds(data.map(d => [d.latitude, d.longitude]));
|
||||
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 14 });
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MUAT DATA DARI API (dengan retry jika gagal)
|
||||
// ============================================================
|
||||
async function muatDataSpbu() {
|
||||
const MAX_RETRY = 3;
|
||||
for (let attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||
try {
|
||||
const data = await apiGetAll();
|
||||
dbReady = true;
|
||||
loadSpbuData(data);
|
||||
showToast(data.length > 0
|
||||
? `✅ ${data.length} SPBU dimuat dari database`
|
||||
: '📭 Database kosong — klik ➕ untuk tambah SPBU', 'success');
|
||||
return; // sukses, keluar
|
||||
} catch (err) {
|
||||
console.warn(`API SPBU gagal (percobaan ${attempt}/${MAX_RETRY}):`, err);
|
||||
if (attempt < MAX_RETRY) {
|
||||
await new Promise(r => setTimeout(r, 800 * attempt)); // tunggu sebentar
|
||||
} else {
|
||||
// Semua retry gagal — mode offline
|
||||
dbReady = false;
|
||||
loadSpbuData(SAMPLE_DATA);
|
||||
const pesan = err.message || 'Tidak bisa terhubung ke server';
|
||||
showToast(`⚠️ Mode offline: ${pesan}`, 'error');
|
||||
console.error('API SPBU gagal setelah semua retry:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tunggu page load penuh sebelum fetch ke API
|
||||
if (document.readyState === 'complete') {
|
||||
muatDataSpbu();
|
||||
} else {
|
||||
window.addEventListener('load', muatDataSpbu);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,293 @@
|
||||
:root {
|
||||
--bg-dark: #0f1117;
|
||||
--border: #2a3248;
|
||||
--accent-green: #10b981;
|
||||
--accent-orange: #f97316;
|
||||
--accent-blue: #3b82f6;
|
||||
--accent-yellow: #f59e0b;
|
||||
--accent-red: #ef4444;
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--text-muted: #64748b;
|
||||
--radius-sm: 6px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
background: var(--bg-dark);
|
||||
color: var(--text-primary);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#header-bar {
|
||||
background: linear-gradient(135deg, #161b27 0%, #0f1117 100%);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 14px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#header-bar h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#header-bar p {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#stats-bar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stat-badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.stat-badge.green {
|
||||
color: var(--accent-green);
|
||||
border-color: rgba(16, 185, 129, 0.35);
|
||||
}
|
||||
|
||||
.stat-badge.orange {
|
||||
color: var(--accent-orange);
|
||||
border-color: rgba(249, 115, 22, 0.35);
|
||||
}
|
||||
|
||||
#map {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.leaflet-control-layers {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35) !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 10px 12px !important;
|
||||
}
|
||||
|
||||
.gis-popup {
|
||||
min-width: 220px;
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-24jam {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.status-tidak24jam {
|
||||
background: rgba(249, 115, 22, 0.15);
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
#toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(80px);
|
||||
background: #1e2538;
|
||||
color: var(--text-primary);
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
font-size: 13px;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#toast.show {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
#toast.error {
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
#toast.success {
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
#toast.info {
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.fab-container {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.fab-btn {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--accent-green), #059669);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.fab-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.fab-btn.active {
|
||||
background: linear-gradient(135deg, var(--accent-orange), #ea580c);
|
||||
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
.popup-form label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.popup-form label:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.popup-form input,
|
||||
.popup-form select {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.popup-form input:focus,
|
||||
.popup-form select:focus {
|
||||
border-color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.popup-btn:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.popup-btn-save {
|
||||
background: var(--accent-green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup-btn-edit {
|
||||
background: var(--accent-yellow);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.popup-btn-delete {
|
||||
background: var(--accent-red);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup-btn-cancel {
|
||||
background: #334155;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
margin: 12px 14px;
|
||||
}
|
||||
Reference in New Issue
Block a user