fitur penanda jalan rusak
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// Script untuk membuat tabel jalan_rusak jika belum ada
|
||||
include 'db_config.php';
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS jalan_rusak (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
latitude DECIMAL(10, 8) NOT NULL,
|
||||
longitude DECIMAL(11, 8) NOT NULL,
|
||||
jenis_kerusakan VARCHAR(100) NOT NULL COMMENT 'Jenis: lubang, retak, ambles, dll',
|
||||
deskripsi TEXT,
|
||||
severity VARCHAR(50) DEFAULT 'sedang' COMMENT 'tertarik: ringan, sedang, berat',
|
||||
gambar VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_coords (latitude, longitude)
|
||||
)";
|
||||
|
||||
if ($conn->query($sql) === TRUE) {
|
||||
echo json_encode(['status' => 'success', 'message' => 'Tabel jalan_rusak berhasil dibuat atau sudah ada']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -6998,7 +6998,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Timur",
|
||||
"name": "Pontianak Timur"
|
||||
"name": "Pontianak Timur",
|
||||
"population": 121500
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -7854,7 +7855,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Selatan",
|
||||
"name": "Pontianak Selatan"
|
||||
"name": "Pontianak Selatan",
|
||||
"population": 133600
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -14352,7 +14354,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Utara",
|
||||
"name": "Pontianak Utara"
|
||||
"name": "Pontianak Utara",
|
||||
"population": 129200
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17096,7 +17099,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Barat",
|
||||
"name": "Pontianak Barat"
|
||||
"name": "Pontianak Barat",
|
||||
"population": 148800
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -18288,7 +18292,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Kota",
|
||||
"name": "Pontianak Kota"
|
||||
"name": "Pontianak Kota",
|
||||
"population": 98500
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -19400,7 +19405,8 @@
|
||||
"Id": 0,
|
||||
"Plan_Area": 113026328.1,
|
||||
"Ket": "Pontianak Tenggara",
|
||||
"name": "Pontianak Tenggara"
|
||||
"name": "Pontianak Tenggara",
|
||||
"population": 52900
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
include 'db_config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = $_POST['id'] ?? '';
|
||||
|
||||
if (empty($id)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'ID wajib diisi']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ambil data gambar untuk dihapus
|
||||
$stmt = $conn->prepare("SELECT gambar FROM jalan_rusak WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$row = $result->fetch_assoc();
|
||||
|
||||
if ($row && $row['gambar'] && file_exists($row['gambar'])) {
|
||||
unlink($row['gambar']);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
|
||||
// Hapus record
|
||||
$stmt = $conn->prepare("DELETE FROM jalan_rusak WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['status' => 'success', 'message' => 'Laporan jalan rusak berhasil dihapus']);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||
$conn->close();
|
||||
?>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
include 'db_config.php';
|
||||
|
||||
$sql = "SELECT id, latitude, longitude, jenis_kerusakan, deskripsi, severity, gambar, created_at
|
||||
FROM jalan_rusak
|
||||
ORDER BY created_at DESC";
|
||||
|
||||
$result = $conn->query($sql);
|
||||
$data = [];
|
||||
|
||||
if (!$result) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$row['latitude'] = (float)$row['latitude'];
|
||||
$row['longitude'] = (float)$row['longitude'];
|
||||
$data[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($data);
|
||||
$conn->close();
|
||||
?>
|
||||
+297
-2
@@ -83,6 +83,9 @@
|
||||
<button class="icon-btn" id="modePolygonBtn" title="Tambah polygon parsil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l8 5v10l-8 5-8-5V7z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeRoadDamageBtn" title="Lapor jalan rusak">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 12h12M12 6v12M2 12h20M12 2v20"/><circle cx="12" cy="12" r="10"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeChoroplethBtn" title="Lihat peta choropleth kecamatan">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z"/><path d="M9 4v14"/><path d="M15 6v14"/></svg>
|
||||
</button>
|
||||
@@ -265,6 +268,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Lapor Jalan Rusak -->
|
||||
<div id="roadDamageModal" class="fixed inset-0 z-[10002] hidden items-center justify-center bg-slate-900/50 px-4">
|
||||
<div class="w-full max-w-lg rounded-2xl bg-white p-5 shadow-2xl">
|
||||
<div class="flex items-start justify-between gap-4 border-b pb-3">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-slate-800">Lapor Jalan Rusak</h3>
|
||||
<p class="text-sm text-slate-500">Dokumentasikan kondisi jalan dengan foto untuk membantu perbaikan.</p>
|
||||
</div>
|
||||
<button id="closeRoadDamageModalBtn" class="rounded-lg bg-slate-100 px-3 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Tutup</button>
|
||||
</div>
|
||||
<div class="mt-4 grid gap-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Jenis Kerusakan</label>
|
||||
<select id="roadDamageType" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-red-500">
|
||||
<option value="">Pilih jenis kerusakan</option>
|
||||
<option value="lubang">Lubang</option>
|
||||
<option value="retak">Retak</option>
|
||||
<option value="ambles">Ambles</option>
|
||||
<option value="bergelombang">Bergelombang</option>
|
||||
<option value="lainnya">Lainnya</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Tingkat Kerusakan</label>
|
||||
<select id="roadDamageSeverity" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-red-500">
|
||||
<option value="ringan">Ringan</option>
|
||||
<option value="sedang" selected>Sedang</option>
|
||||
<option value="berat">Berat</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Deskripsi</label>
|
||||
<textarea id="roadDamageDesc" placeholder="Jelaskan kondisi jalan, lokasi spesifik, dll..." class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-red-500" rows="3"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Foto Kondisi (Opsional)</label>
|
||||
<input id="roadDamageImage" type="file" accept="image/*" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
|
||||
<div id="imagePreview" class="mt-2 hidden">
|
||||
<img id="previewImg" src="" alt="Preview" class="max-h-40 rounded-lg">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] text-slate-500 bg-slate-50 p-2 rounded">
|
||||
📍 Koordinat: <span id="roadDamageCoords">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex flex-wrap gap-2 border-t pt-4">
|
||||
<button id="saveRoadDamageBtn" class="rounded-lg bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700">Simpan Laporan</button>
|
||||
<button id="cancelRoadDamageBtn" class="rounded-lg bg-slate-100 px-4 py-2 text-sm font-bold text-slate-700 hover:bg-slate-200">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<select id="featureType" class="hidden">
|
||||
<option value="polyline">polyline</option>
|
||||
<option value="polygon">polygon</option>
|
||||
@@ -299,6 +354,9 @@
|
||||
let featureRecords = [];
|
||||
let editingFeatureId = null;
|
||||
let choroplethLayer = null;
|
||||
let roadDamageLayer = L.featureGroup().addTo(map);
|
||||
let roadDamageRecords = [];
|
||||
let pendingRoadDamageCoords = null;
|
||||
|
||||
const FEATURE_STATUS_OPTIONS = {
|
||||
polyline: [
|
||||
@@ -316,6 +374,40 @@
|
||||
|
||||
const CHOROPLETH_GEOJSON_PATH = 'data/pontianak-kecamatan.geojson';
|
||||
let choroplethGeoJsonData = null;
|
||||
const DISTRICT_POPULATION_MAP = {
|
||||
'pontianak barat': 148800,
|
||||
'pontianak kota': 98500,
|
||||
'pontianak selatan': 133600,
|
||||
'pontianak tenggara': 52900,
|
||||
'pontianak timur': 121500,
|
||||
'pontianak utara': 129200
|
||||
};
|
||||
|
||||
function normalizeDistrictName(name) {
|
||||
return String(name || '')
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function enrichFeaturePopulation(feature) {
|
||||
const properties = feature.properties || {};
|
||||
const districtName = properties.name || properties.NAME || properties.Ket || '';
|
||||
const districtKey = normalizeDistrictName(districtName);
|
||||
const mappedPopulation = DISTRICT_POPULATION_MAP[districtKey];
|
||||
|
||||
if (mappedPopulation == null) {
|
||||
return feature;
|
||||
}
|
||||
|
||||
return {
|
||||
...feature,
|
||||
properties: {
|
||||
...properties,
|
||||
population: Number(mappedPopulation)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getPopulationFill(population) {
|
||||
if (population > 120000) return '#ef4444';
|
||||
@@ -418,7 +510,9 @@
|
||||
function sanitizeChoroplethGeoJson(data) {
|
||||
return {
|
||||
...data,
|
||||
features: data.features.map(sanitizeChoroplethFeature)
|
||||
features: data.features
|
||||
.map(sanitizeChoroplethFeature)
|
||||
.map(enrichFeaturePopulation)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -765,7 +859,8 @@
|
||||
point: document.getElementById('modePointBtn'),
|
||||
polyline: document.getElementById('modePolylineBtn'),
|
||||
polygon: document.getElementById('modePolygonBtn'),
|
||||
choropleth: document.getElementById('modeChoroplethBtn')
|
||||
choropleth: document.getElementById('modeChoroplethBtn'),
|
||||
roadDamage: document.getElementById('modeRoadDamageBtn')
|
||||
};
|
||||
|
||||
Object.values(modeButtons).forEach(button => button.classList.remove('active'));
|
||||
@@ -797,6 +892,18 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'roadDamage') {
|
||||
hint.innerText = 'Mode laporan jalan rusak aktif: klik peta untuk menandai lokasi kerusakan.';
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Klik Peta';
|
||||
runActionBtn.disabled = true;
|
||||
clearDraftBtn.disabled = true;
|
||||
updateDrawControl(null);
|
||||
document.getElementById('modeMeasure').value = '';
|
||||
loadRoadDamage(true);
|
||||
return;
|
||||
}
|
||||
|
||||
runActionBtn.disabled = false;
|
||||
clearDraftBtn.disabled = false;
|
||||
|
||||
@@ -818,6 +925,163 @@
|
||||
setSpatialType(mode);
|
||||
}
|
||||
|
||||
function openRoadDamageModal(lat, lng) {
|
||||
pendingRoadDamageCoords = { lat, lng };
|
||||
document.getElementById('roadDamageCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||
document.getElementById('roadDamageType').value = '';
|
||||
document.getElementById('roadDamageSeverity').value = 'sedang';
|
||||
document.getElementById('roadDamageDesc').value = '';
|
||||
document.getElementById('roadDamageImage').value = '';
|
||||
document.getElementById('imagePreview').classList.add('hidden');
|
||||
const modal = document.getElementById('roadDamageModal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
suppressAutoReload = true;
|
||||
}
|
||||
|
||||
function closeRoadDamageModal() {
|
||||
const modal = document.getElementById('roadDamageModal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
pendingRoadDamageCoords = null;
|
||||
suppressAutoReload = !!document.querySelector('[id^="edit-mode-"]:not(.hidden)') || !!editingFeatureId;
|
||||
}
|
||||
|
||||
function saveRoadDamage() {
|
||||
if (!pendingRoadDamageCoords) {
|
||||
showMessage('Koordinat tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
const jenis = document.getElementById('roadDamageType').value;
|
||||
if (!jenis) {
|
||||
showMessage('Pilih jenis kerusakan terlebih dahulu', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
const severity = document.getElementById('roadDamageSeverity').value;
|
||||
const deskripsi = document.getElementById('roadDamageDesc').value;
|
||||
const fileInput = document.getElementById('roadDamageImage');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('latitude', pendingRoadDamageCoords.lat);
|
||||
formData.append('longitude', pendingRoadDamageCoords.lng);
|
||||
formData.append('jenis_kerusakan', jenis);
|
||||
formData.append('severity', severity);
|
||||
formData.append('deskripsi', deskripsi);
|
||||
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
formData.append('gambar', fileInput.files[0]);
|
||||
}
|
||||
|
||||
fetch('save_jalan_rusak.php', { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, 'save_jalan_rusak.php'))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('✅ Laporan jalan rusak berhasil disimpan', 'bg-green-600 text-white');
|
||||
closeRoadDamageModal();
|
||||
loadRoadDamage(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menyimpan laporan');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menyimpan laporan', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function createRoadDamageIcon(severity) {
|
||||
const colorMap = {
|
||||
'ringan': '#fbbf24',
|
||||
'sedang': '#f97316',
|
||||
'berat': '#dc2626'
|
||||
};
|
||||
const color = colorMap[severity] || '#f97316';
|
||||
|
||||
return L.divIcon({
|
||||
className: "custom-marker",
|
||||
html: `<svg width="32" height="40" viewBox="0 0 32 40" fill="${color}" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 0C8.27 0 2 6.27 2 14c0 8 14 26 14 26s14-18 14-26c0-7.73-6.27-14-14-14z"/>
|
||||
<circle cx="16" cy="14" r="5" fill="white"/>
|
||||
<text x="16" y="16" text-anchor="middle" dy=".3em" font-size="10" font-weight="bold" fill="${color}">!</text>
|
||||
</svg>`,
|
||||
iconSize: [32, 40],
|
||||
iconAnchor: [16, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function getRoadDamagePopup(record) {
|
||||
const imageHtml = record.gambar ? `<img src="${record.gambar}" alt="Foto jalan rusak" class="mt-2 w-full rounded-lg max-h-48 object-cover">` : '';
|
||||
|
||||
return `
|
||||
<div class="w-full p-3">
|
||||
<h4 class="font-bold text-slate-800">Laporan Jalan Rusak</h4>
|
||||
<p class="mt-1 text-sm text-slate-600"><strong>Jenis:</strong> ${record.jenis_kerusakan}</p>
|
||||
<p class="text-sm text-slate-600"><strong>Tingkat:</strong>
|
||||
<span class="px-2 py-1 rounded text-xs font-bold ${
|
||||
record.severity === 'ringan' ? 'bg-yellow-100 text-yellow-700' :
|
||||
record.severity === 'sedang' ? 'bg-orange-100 text-orange-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}">${record.severity}</span>
|
||||
</p>
|
||||
${record.deskripsi ? `<p class="mt-1 text-sm text-slate-600"><strong>Deskripsi:</strong> ${record.deskripsi}</p>` : ''}
|
||||
<p class="mt-1 text-xs text-slate-500">📍 ${record.latitude.toFixed(5)}, ${record.longitude.toFixed(5)}</p>
|
||||
${imageHtml}
|
||||
<div class="mt-3 flex gap-2 border-t pt-2">
|
||||
<button onclick="deleteRoadDamage(${record.id})" class="flex-1 rounded bg-red-50 px-2 py-1 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function loadRoadDamage(force = false) {
|
||||
if (!force && (document.hidden || suppressAutoReload)) return;
|
||||
|
||||
fetch('get_jalan_rusak.php')
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('HTTP Error: ' + res.status);
|
||||
return res.text();
|
||||
})
|
||||
.then(text => {
|
||||
const data = JSON.parse(text);
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Format data tidak valid');
|
||||
}
|
||||
|
||||
roadDamageRecords = data;
|
||||
roadDamageLayer.clearLayers();
|
||||
|
||||
data.forEach(record => {
|
||||
const marker = L.marker([record.latitude, record.longitude], {
|
||||
icon: createRoadDamageIcon(record.severity)
|
||||
}).addTo(roadDamageLayer);
|
||||
|
||||
marker.bindPopup(getRoadDamagePopup(record));
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Gagal memuat data jalan rusak:', err);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRoadDamage(id) {
|
||||
if (!confirm('Hapus laporan jalan rusak ini?')) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
|
||||
fetch('delete_jalan_rusak.php', { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, 'delete_jalan_rusak.php'))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('🗑️ Laporan berhasil dihapus', 'bg-orange-600 text-white');
|
||||
loadRoadDamage(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menghapus laporan');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menghapus laporan', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function updateFeatureMeasurePreview() {
|
||||
const type = document.getElementById('featureType').value;
|
||||
const layer = pendingShape;
|
||||
@@ -1646,6 +1910,12 @@
|
||||
// Klik Peta untuk Input Baru
|
||||
map.on('click', function(e) {
|
||||
if (isFeatureDrawing) return;
|
||||
|
||||
if (appMode === 'roadDamage') {
|
||||
openRoadDamageModal(e.latlng.lat, e.latlng.lng);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appMode !== 'point') return;
|
||||
|
||||
const lat = e.latlng.lat;
|
||||
@@ -1754,6 +2024,7 @@
|
||||
document.getElementById('modePolylineBtn').addEventListener('click', () => setDataMode('polyline'));
|
||||
document.getElementById('modePolygonBtn').addEventListener('click', () => setDataMode('polygon'));
|
||||
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
|
||||
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
|
||||
document.getElementById('drawStatus').addEventListener('change', () => {
|
||||
const type = document.getElementById('featureType').value;
|
||||
document.getElementById('featureStatus').value = document.getElementById('drawStatus').value;
|
||||
@@ -1831,6 +2102,30 @@
|
||||
document.getElementById('newFeatureModal').addEventListener('click', (event) => {
|
||||
if (event.target.id === 'newFeatureModal') closeNewFeatureModal(true);
|
||||
});
|
||||
|
||||
// Road Damage Modal Event Listeners
|
||||
document.getElementById('saveRoadDamageBtn').addEventListener('click', saveRoadDamage);
|
||||
document.getElementById('cancelRoadDamageBtn').addEventListener('click', closeRoadDamageModal);
|
||||
document.getElementById('closeRoadDamageModalBtn').addEventListener('click', closeRoadDamageModal);
|
||||
document.getElementById('roadDamageImage').addEventListener('change', (event) => {
|
||||
const file = event.target.files[0];
|
||||
const preview = document.getElementById('imagePreview');
|
||||
const previewImg = document.getElementById('previewImg');
|
||||
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
previewImg.src = e.target.result;
|
||||
preview.classList.remove('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
preview.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
document.getElementById('roadDamageModal').addEventListener('click', (event) => {
|
||||
if (event.target.id === 'roadDamageModal') closeRoadDamageModal();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
include 'db_config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$latitude = $_POST['latitude'] ?? '';
|
||||
$longitude = $_POST['longitude'] ?? '';
|
||||
$jenis_kerusakan = $_POST['jenis_kerusakan'] ?? '';
|
||||
$deskripsi = $_POST['deskripsi'] ?? '';
|
||||
$severity = $_POST['severity'] ?? 'sedang';
|
||||
|
||||
if (empty($latitude) || empty($longitude) || empty($jenis_kerusakan)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Latitude, longitude, dan jenis kerusakan wajib diisi']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$gambar = null;
|
||||
|
||||
// Handle file upload
|
||||
if (isset($_FILES['gambar']) && $_FILES['gambar']['error'] === UPLOAD_ERR_OK) {
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
|
||||
$filename = $_FILES['gambar']['name'];
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
|
||||
if (!in_array($ext, $allowed)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Format gambar hanya JPG, PNG, GIF']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Buat folder uploads jika belum ada
|
||||
if (!is_dir('uploads')) {
|
||||
mkdir('uploads', 0755, true);
|
||||
}
|
||||
|
||||
// Generate nama file unik
|
||||
$gambar = 'uploads/' . time() . '_' . basename($filename);
|
||||
|
||||
if (!move_uploaded_file($_FILES['gambar']['tmp_name'], $gambar)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Gagal upload gambar']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO jalan_rusak (latitude, longitude, jenis_kerusakan, deskripsi, severity, gambar) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
|
||||
if (!$stmt) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => $conn->error]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt->bind_param("ddssss", $latitude, $longitude, $jenis_kerusakan, $deskripsi, $severity, $gambar);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
echo json_encode(['status' => 'success', 'message' => 'Laporan jalan rusak berhasil disimpan', 'id' => $stmt->insert_id]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Method tidak diizinkan']);
|
||||
$conn->close();
|
||||
?>
|
||||
Reference in New Issue
Block a user