fitur penanda jalan rusak

This commit is contained in:
Farha
2026-04-14 10:52:05 +07:00
parent eee1e89cbc
commit aa3fe296c1
6 changed files with 480 additions and 8 deletions
+24
View File
@@ -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();
?>
+12 -6
View File
@@ -6998,7 +6998,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Timur", "Ket": "Pontianak Timur",
"name": "Pontianak Timur" "name": "Pontianak Timur",
"population": 121500
} }
}, },
{ {
@@ -7854,7 +7855,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Selatan", "Ket": "Pontianak Selatan",
"name": "Pontianak Selatan" "name": "Pontianak Selatan",
"population": 133600
} }
}, },
{ {
@@ -14352,7 +14354,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Utara", "Ket": "Pontianak Utara",
"name": "Pontianak Utara" "name": "Pontianak Utara",
"population": 129200
} }
}, },
{ {
@@ -17096,7 +17099,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Barat", "Ket": "Pontianak Barat",
"name": "Pontianak Barat" "name": "Pontianak Barat",
"population": 148800
} }
}, },
{ {
@@ -18288,7 +18292,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Kota", "Ket": "Pontianak Kota",
"name": "Pontianak Kota" "name": "Pontianak Kota",
"population": 98500
} }
}, },
{ {
@@ -19400,7 +19405,8 @@
"Id": 0, "Id": 0,
"Plan_Area": 113026328.1, "Plan_Area": 113026328.1,
"Ket": "Pontianak Tenggara", "Ket": "Pontianak Tenggara",
"name": "Pontianak Tenggara" "name": "Pontianak Tenggara",
"population": 52900
} }
} }
] ]
+46
View File
@@ -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();
?>
+29
View File
@@ -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
View File
@@ -83,6 +83,9 @@
<button class="icon-btn" id="modePolygonBtn" title="Tambah polygon parsil"> <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> <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>
<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"> <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> <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> </button>
@@ -265,6 +268,58 @@
</div> </div>
</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"> <select id="featureType" class="hidden">
<option value="polyline">polyline</option> <option value="polyline">polyline</option>
<option value="polygon">polygon</option> <option value="polygon">polygon</option>
@@ -299,6 +354,9 @@
let featureRecords = []; let featureRecords = [];
let editingFeatureId = null; let editingFeatureId = null;
let choroplethLayer = null; let choroplethLayer = null;
let roadDamageLayer = L.featureGroup().addTo(map);
let roadDamageRecords = [];
let pendingRoadDamageCoords = null;
const FEATURE_STATUS_OPTIONS = { const FEATURE_STATUS_OPTIONS = {
polyline: [ polyline: [
@@ -316,6 +374,40 @@
const CHOROPLETH_GEOJSON_PATH = 'data/pontianak-kecamatan.geojson'; const CHOROPLETH_GEOJSON_PATH = 'data/pontianak-kecamatan.geojson';
let choroplethGeoJsonData = null; 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) { function getPopulationFill(population) {
if (population > 120000) return '#ef4444'; if (population > 120000) return '#ef4444';
@@ -418,7 +510,9 @@
function sanitizeChoroplethGeoJson(data) { function sanitizeChoroplethGeoJson(data) {
return { return {
...data, ...data,
features: data.features.map(sanitizeChoroplethFeature) features: data.features
.map(sanitizeChoroplethFeature)
.map(enrichFeaturePopulation)
}; };
} }
@@ -765,7 +859,8 @@
point: document.getElementById('modePointBtn'), point: document.getElementById('modePointBtn'),
polyline: document.getElementById('modePolylineBtn'), polyline: document.getElementById('modePolylineBtn'),
polygon: document.getElementById('modePolygonBtn'), 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')); Object.values(modeButtons).forEach(button => button.classList.remove('active'));
@@ -797,6 +892,18 @@
return; 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; runActionBtn.disabled = false;
clearDraftBtn.disabled = false; clearDraftBtn.disabled = false;
@@ -818,6 +925,163 @@
setSpatialType(mode); 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() { function updateFeatureMeasurePreview() {
const type = document.getElementById('featureType').value; const type = document.getElementById('featureType').value;
const layer = pendingShape; const layer = pendingShape;
@@ -1646,6 +1910,12 @@
// Klik Peta untuk Input Baru // Klik Peta untuk Input Baru
map.on('click', function(e) { map.on('click', function(e) {
if (isFeatureDrawing) return; if (isFeatureDrawing) return;
if (appMode === 'roadDamage') {
openRoadDamageModal(e.latlng.lat, e.latlng.lng);
return;
}
if (appMode !== 'point') return; if (appMode !== 'point') return;
const lat = e.latlng.lat; const lat = e.latlng.lat;
@@ -1754,6 +2024,7 @@
document.getElementById('modePolylineBtn').addEventListener('click', () => setDataMode('polyline')); document.getElementById('modePolylineBtn').addEventListener('click', () => setDataMode('polyline'));
document.getElementById('modePolygonBtn').addEventListener('click', () => setDataMode('polygon')); document.getElementById('modePolygonBtn').addEventListener('click', () => setDataMode('polygon'));
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth')); document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
document.getElementById('drawStatus').addEventListener('change', () => { document.getElementById('drawStatus').addEventListener('change', () => {
const type = document.getElementById('featureType').value; const type = document.getElementById('featureType').value;
document.getElementById('featureStatus').value = document.getElementById('drawStatus').value; document.getElementById('featureStatus').value = document.getElementById('drawStatus').value;
@@ -1831,6 +2102,30 @@
document.getElementById('newFeatureModal').addEventListener('click', (event) => { document.getElementById('newFeatureModal').addEventListener('click', (event) => {
if (event.target.id === 'newFeatureModal') closeNewFeatureModal(true); 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> </script>
</body> </body>
</html> </html>
+72
View File
@@ -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();
?>