Initial commit - Fresh Clean Code
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
https://sigwebsite.free.je/JalanRusak_Marker/
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
$action = isset($_GET['action']) ? $_GET['action'] : '';
|
||||||
|
|
||||||
|
// Folder untuk menyimpan foto
|
||||||
|
$upload_dir = 'uploads/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($method === 'GET' && $action === 'getAll') {
|
||||||
|
$result = $conn->query("SELECT * FROM jalan_rusak ORDER BY created_at DESC");
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
sendResponse(true, 'Data berhasil dimuat', $data);
|
||||||
|
}
|
||||||
|
elseif ($method === 'POST' && $action === 'create') {
|
||||||
|
$nama = $_POST['nama'] ?? '';
|
||||||
|
$lat = $_POST['latitude'] ?? 0;
|
||||||
|
$lng = $_POST['longitude'] ?? 0;
|
||||||
|
$alamat = $_POST['alamat'] ?? '';
|
||||||
|
$catatan = $_POST['catatan'] ?? '';
|
||||||
|
|
||||||
|
$foto_name = '';
|
||||||
|
// Logika Upload File Foto
|
||||||
|
if (isset($_FILES['foto_file']) && $_FILES['foto_file']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$ext = pathinfo($_FILES['foto_file']['name'], PATHINFO_EXTENSION);
|
||||||
|
$foto_name = 'img_' . time() . '.' . $ext;
|
||||||
|
move_uploaded_file($_FILES['foto_file']['tmp_name'], $upload_dir . $foto_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO jalan_rusak (nama, latitude, longitude, alamat, catatan, foto) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("sddsss", $nama, $lat, $lng, $alamat, $catatan, $foto_name);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
sendResponse(true, 'Laporan berhasil disimpan');
|
||||||
|
} else {
|
||||||
|
sendResponse(false, 'Gagal menyimpan ke database');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($method === 'DELETE' && $action === 'delete') {
|
||||||
|
$id = intval($_GET['id']);
|
||||||
|
// Opsional: Hapus file fisik foto di sini jika diperlukan
|
||||||
|
$stmt = $conn->prepare("DELETE FROM jalan_rusak WHERE id=?");
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
sendResponse(true, 'Laporan dihapus');
|
||||||
|
}
|
||||||
|
?>
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
// config.php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type');
|
||||||
|
|
||||||
|
$host = 'localhost';
|
||||||
|
$username = 'root';
|
||||||
|
$password = '';
|
||||||
|
$database = 'gis_management';
|
||||||
|
$port = 3307; // Sesuaikan port XAMPP Anda
|
||||||
|
|
||||||
|
$conn = new mysqli($host, $username, $password, $database, $port);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die(json_encode(['success' => false, 'message' => 'Koneksi gagal: ' . $conn->connect_error]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->set_charset("utf8");
|
||||||
|
|
||||||
|
function sendResponse($success, $message, $data = null) {
|
||||||
|
echo json_encode(['success' => $success, 'message' => $message, 'data' => $data]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Sistem Pelaporan Jalan Rusak V3 - Full Draggable</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
#map { height: 600px; width: 100%; border-radius: 15px; border: 2px solid #dee2e6; }
|
||||||
|
.sidebar-container { height: 850px; display: flex; flex-direction: column; background: white; padding: 20px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
|
||||||
|
.form-section { border-bottom: 2px solid #f8f9fa; padding-bottom: 20px; margin-bottom: 20px; }
|
||||||
|
.list-section { flex-grow: 1; overflow-y: auto; padding-right: 5px; }
|
||||||
|
|
||||||
|
.report-card {
|
||||||
|
border: 1px solid #eee; border-radius: 10px; padding: 15px;
|
||||||
|
margin-bottom: 15px; transition: 0.3s; cursor: pointer;
|
||||||
|
}
|
||||||
|
.report-card:hover { border-color: #ffc107; background: #fffdf5; transform: translateY(-2px); }
|
||||||
|
|
||||||
|
.thumb-img { width: 100%; height: 160px; object-fit: cover; border-radius: 8px; margin-top: 10px; }
|
||||||
|
.address-text { font-size: 0.85rem; color: #666; display: block; margin-top: 5px; }
|
||||||
|
|
||||||
|
#catatan { min-height: 120px; resize: vertical; }
|
||||||
|
|
||||||
|
.btn-add-mode {
|
||||||
|
margin-top: 15px;
|
||||||
|
border: 2px dashed #dc3545;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.btn-active {
|
||||||
|
background-color: #dc3545 !important;
|
||||||
|
color: white !important;
|
||||||
|
border-style: solid;
|
||||||
|
box-shadow: 0 0 15px rgba(220, 53, 69, 0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-7">
|
||||||
|
<div id="map" class="shadow-sm"></div>
|
||||||
|
|
||||||
|
<button id="btnEnableAdd" class="btn btn-outline-danger w-100 btn-add-mode py-3">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i> KLIK UNTUK AKTIFKAN MODE TAMBAH MARKER
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="alert alert-info mt-3 small">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
<strong>Tips:</strong> Semua marker (baru maupun lama) dapat digeser untuk menyesuaikan posisi koordinat secara presisi.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="sidebar-container">
|
||||||
|
<div class="form-section">
|
||||||
|
<h4 class="fw-bold text-dark mb-3"><i class="fas fa-file-signature text-warning"></i> Detail Laporan</h4>
|
||||||
|
<form id="uploadForm">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="small fw-bold">Nama Jalan / Lokasi</label>
|
||||||
|
<input type="text" name="nama" id="nama" class="form-control" placeholder="Masukan nama lokasi" required>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
<label class="small fw-bold">Latitude</label>
|
||||||
|
<input type="text" name="latitude" id="lat" class="form-control form-control-sm bg-light" readonly required>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="small fw-bold">Longitude</label>
|
||||||
|
<input type="text" name="longitude" id="lng" class="form-control form-control-sm bg-light" readonly required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="small fw-bold text-secondary">Alamat Terdeteksi</label>
|
||||||
|
<textarea name="alamat" id="alamat" class="form-control form-control-sm bg-light" rows="2" readonly placeholder="Klik/Geser marker untuk mendapatkan alamat..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="small fw-bold">Catatan Kerusakan</label>
|
||||||
|
<textarea name="catatan" id="catatan" class="form-control" placeholder="Detail kerusakan..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="small fw-bold">Upload Foto</label>
|
||||||
|
<input type="file" name="foto_file" id="foto_file" class="form-control" accept="image/*" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100 fw-bold py-2 shadow-sm text-dark" id="btnSimpan" disabled>
|
||||||
|
<i class="fas fa-save me-2"></i> SIMPAN LAPORAN BARU
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-section">
|
||||||
|
<h5 class="fw-bold mb-3">Daftar Jalan Rusak</h5>
|
||||||
|
<div id="reportList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
const map = L.map('map').setView([-0.026330, 109.342519], 13);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||||
|
|
||||||
|
const warningIcon = L.icon({
|
||||||
|
iconUrl: 'https://cdn-icons-png.flaticon.com/512/564/564619.png',
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 40]
|
||||||
|
});
|
||||||
|
|
||||||
|
let inputMarker = null;
|
||||||
|
let addMode = false;
|
||||||
|
let markerLayers = L.layerGroup().addTo(map); // Menggunakan layer group agar mudah dibersihkan
|
||||||
|
|
||||||
|
const btnAdd = document.getElementById('btnEnableAdd');
|
||||||
|
btnAdd.onclick = function() {
|
||||||
|
addMode = true;
|
||||||
|
this.classList.add('btn-active');
|
||||||
|
this.innerHTML = "<i class='fas fa-mouse-pointer me-2'></i> MODE AKTIF: KLIK PADA PETA";
|
||||||
|
map.getContainer().style.cursor = 'crosshair';
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateGeocode(lat, lng, targetId = 'alamat') {
|
||||||
|
return fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const addr = data.display_name || "Alamat tidak ditemukan";
|
||||||
|
if(document.getElementById(targetId)) {
|
||||||
|
document.getElementById(targetId).value = addr;
|
||||||
|
}
|
||||||
|
return addr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE KOORDINAT KE DATABASE SAAT MARKER LAMA DIGESER
|
||||||
|
async function updateMarkerLocation(id, lat, lng) {
|
||||||
|
const alamatBaru = await updateGeocode(lat, lng, null); // dapatkan alamat baru tanpa mengisi form
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
formData.append('latitude', lat);
|
||||||
|
formData.append('longitude', lng);
|
||||||
|
formData.append('alamat', alamatBaru);
|
||||||
|
|
||||||
|
// Pastikan api_jalan_rusak.php mendukung action 'updateCoords' atau sesuaikan logicnya
|
||||||
|
fetch(`api_jalan_rusak.php?action=updateCoords`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(res => res.json()).then(res => {
|
||||||
|
console.log("Posisi diperbarui di database");
|
||||||
|
loadReports(); // Reload list untuk sinkronisasi alamat
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// KLIK PETA (MARKER BARU)
|
||||||
|
map.on('click', function(e) {
|
||||||
|
if (!addMode) return;
|
||||||
|
const { lat, lng } = e.latlng;
|
||||||
|
|
||||||
|
document.getElementById('lat').value = lat.toFixed(7);
|
||||||
|
document.getElementById('lng').value = lng.toFixed(7);
|
||||||
|
document.getElementById('btnSimpan').disabled = false;
|
||||||
|
|
||||||
|
if (inputMarker) map.removeLayer(inputMarker);
|
||||||
|
|
||||||
|
inputMarker = L.marker([lat, lng], {
|
||||||
|
icon: warningIcon,
|
||||||
|
draggable: true
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
updateGeocode(lat, lng);
|
||||||
|
|
||||||
|
inputMarker.on('dragend', function(event) {
|
||||||
|
const pos = event.target.getLatLng();
|
||||||
|
document.getElementById('lat').value = pos.lat.toFixed(7);
|
||||||
|
document.getElementById('lng').value = pos.lng.toFixed(7);
|
||||||
|
updateGeocode(pos.lat, pos.lng);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// LOAD DATA DARI DATABASE
|
||||||
|
async function loadReports() {
|
||||||
|
const res = await fetch('api_jalan_rusak.php?action=getAll');
|
||||||
|
const json = await res.json();
|
||||||
|
const listContainer = document.getElementById('reportList');
|
||||||
|
listContainer.innerHTML = '';
|
||||||
|
markerLayers.clearLayers(); // Bersihkan marker lama sebelum load ulang
|
||||||
|
|
||||||
|
json.data.forEach(item => {
|
||||||
|
// BUAT MARKER YANG SUDAH ADA MENJADI DRAGGABLE JUGA
|
||||||
|
const m = L.marker([item.latitude, item.longitude], {
|
||||||
|
icon: warningIcon,
|
||||||
|
draggable: true
|
||||||
|
}).addTo(markerLayers);
|
||||||
|
|
||||||
|
// Jika marker lama digeser, simpan perubahan posisi ke database
|
||||||
|
m.on('dragend', function(event) {
|
||||||
|
const pos = event.target.getLatLng();
|
||||||
|
if(confirm("Simpan perubahan posisi untuk lokasi ini?")) {
|
||||||
|
updateMarkerLocation(item.id, pos.lat.toFixed(7), pos.lng.toFixed(7));
|
||||||
|
} else {
|
||||||
|
loadReports(); // Kembalikan ke posisi semula jika batal
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'report-card';
|
||||||
|
card.onclick = () => map.flyTo([item.latitude, item.longitude], 17);
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="fw-bold mb-0 text-dark"><i class="fas fa-map-marker-alt text-danger me-2"></i>${item.nama}</h6>
|
||||||
|
<button class="btn btn-sm text-danger" onclick="event.stopPropagation(); deleteReport(${item.id})">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<small class="address-text">${item.alamat}</small>
|
||||||
|
${item.foto ? `<img src="uploads/${item.foto}" class="thumb-img">` : ''}
|
||||||
|
`;
|
||||||
|
listContainer.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIMPAN DATA BARU
|
||||||
|
document.getElementById('uploadForm').onsubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const res = await fetch('api_jalan_rusak.php?action=create', { method: 'POST', body: formData });
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
alert("Laporan Berhasil!");
|
||||||
|
e.target.reset();
|
||||||
|
addMode = false;
|
||||||
|
document.getElementById('btnSimpan').disabled = true;
|
||||||
|
btnAdd.classList.remove('btn-active');
|
||||||
|
btnAdd.innerHTML = "<i class='fas fa-exclamation-triangle me-2'></i> KLIK UNTUK AKTIFKAN MODE TAMBAH MARKER";
|
||||||
|
if (inputMarker) map.removeLayer(inputMarker);
|
||||||
|
map.getContainer().style.cursor = '';
|
||||||
|
loadReports();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function deleteReport(id) {
|
||||||
|
if(confirm('Hapus laporan?')) {
|
||||||
|
await fetch(`api_jalan_rusak.php?action=delete&id=${id}`, { method: 'DELETE' });
|
||||||
|
loadReports();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadReports();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
Reference in New Issue
Block a user