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