first
This commit is contained in:
+748
-18
@@ -155,6 +155,12 @@
|
||||
<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="modeVerifikasiBtn" title="Verifikasi lapangan">
|
||||
<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="M20 6 9 17l-5-5"/><path d="M4 4h16v16H4z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="modeAduanWargaBtn" title="Aduan warga">
|
||||
<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="M7 8h10"/><path d="M7 12h6"/><path d="M20 16.5A2.5 2.5 0 0 1 17.5 19H8l-4 3V6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5z"/></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>
|
||||
@@ -251,6 +257,12 @@
|
||||
<button class="icon-btn" id="miniModePendudukMiskinBtn" title="Input penduduk miskin">
|
||||
<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 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4z"/><path d="M4 21a8 8 0 0 1 16 0"/><path d="M18 8h4"/><path d="M20 6v4"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="miniModeVerifikasiBtn" title="Verifikasi lapangan">
|
||||
<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="M20 6 9 17l-5-5"/><path d="M4 4h16v16H4z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="miniModeAduanWargaBtn" title="Aduan warga">
|
||||
<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="M7 8h10"/><path d="M7 12h6"/><path d="M20 16.5A2.5 2.5 0 0 1 17.5 19H8l-4 3V6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5z"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="toggleRegistryDrawerBtn" title="Buka registry & filter">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
|
||||
</button>
|
||||
@@ -401,6 +413,14 @@
|
||||
<input id="layerToggle-pendudukMiskinLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>Penduduk Miskin</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||
<input id="layerToggle-verifikasiLapanganLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>Verifikasi Lapangan</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||
<input id="layerToggle-aduanWargaLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>Aduan Warga</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2">
|
||||
<input id="layerToggle-roadNetworkLayer" type="checkbox" class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>Jalan Eksternal</span>
|
||||
@@ -644,6 +664,128 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="verifikasiLapanganModal" class="fixed inset-0 z-[10005] hidden items-center justify-center bg-slate-900/50 px-4">
|
||||
<div class="w-full max-w-xl 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">Verifikasi Lapangan</h3>
|
||||
<p class="text-sm text-slate-500">Catat hasil cek lapangan untuk data kemiskinan atau infrastruktur.</p>
|
||||
</div>
|
||||
<button id="closeVerifikasiLapanganModalBtn" 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 sm:grid-cols-2">
|
||||
<input id="verifikasiLapanganId" type="hidden">
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Target Data</label>
|
||||
<select id="verifikasiJenisTarget" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||
<option value="penduduk_miskin">Penduduk Miskin</option>
|
||||
<option value="jalan_rusak">Jalan Rusak</option>
|
||||
<option value="rumah_ibadah">Rumah Ibadah</option>
|
||||
<option value="lainnya">Lainnya</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Target</label>
|
||||
<input id="verifikasiTargetNama" type="text" placeholder="Contoh: KK A / Jalan RW 03" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Petugas</label>
|
||||
<input id="verifikasiPetugasNama" type="text" placeholder="Nama surveyor" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Status</label>
|
||||
<select id="verifikasiStatus" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500">
|
||||
<option value="menunggu">Menunggu</option>
|
||||
<option value="valid">Valid</option>
|
||||
<option value="perlu_tindak_lanjut">Perlu Tindak Lanjut</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Hasil Temuan</label>
|
||||
<textarea id="verifikasiHasilTemuan" rows="3" placeholder="Ringkasan hasil cek lapangan" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500"></textarea>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Tindak Lanjut</label>
|
||||
<textarea id="verifikasiTindakLanjut" rows="2" placeholder="Catatan tindak lanjut" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-emerald-500"></textarea>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Foto (Opsional)</label>
|
||||
<input id="verifikasiFoto" type="file" accept="image/*" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2 text-[10px] text-slate-500 bg-slate-50 p-2 rounded">
|
||||
📍 Koordinat: <span id="verifikasiCoords">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex flex-wrap gap-2 border-t pt-4">
|
||||
<button id="saveVerifikasiLapanganBtn" class="rounded-lg bg-emerald-600 px-4 py-2 text-sm font-bold text-white hover:bg-emerald-700">Simpan Verifikasi</button>
|
||||
<button id="cancelVerifikasiLapanganBtn" 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>
|
||||
|
||||
<div id="aduanWargaModal" class="fixed inset-0 z-[10006] hidden items-center justify-center bg-slate-900/50 px-4">
|
||||
<div class="w-full max-w-xl 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">Aduan Warga</h3>
|
||||
<p class="text-sm text-slate-500">Terima aduan berbasis lokasi untuk memprioritaskan respon lapangan.</p>
|
||||
</div>
|
||||
<button id="closeAduanWargaModalBtn" 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 sm:grid-cols-2">
|
||||
<input id="aduanWargaId" type="hidden">
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Kategori</label>
|
||||
<select id="aduanKategori" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||
<option value="">Pilih kategori</option>
|
||||
<option value="jalan rusak">Jalan Rusak</option>
|
||||
<option value="banjir">Banjir</option>
|
||||
<option value="rumah tidak layak">Rumah Tidak Layak</option>
|
||||
<option value="air bersih">Air Bersih</option>
|
||||
<option value="sanitasi">Sanitasi</option>
|
||||
<option value="akses bantuan">Akses Bantuan</option>
|
||||
<option value="lainnya">Lainnya</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Nama Pelapor</label>
|
||||
<input id="aduanPelaporNama" type="text" placeholder="Nama warga" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Kontak</label>
|
||||
<input id="aduanKontakPelapor" type="text" placeholder="Nomor HP / WA" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Status</label>
|
||||
<select id="aduanStatus" class="w-full rounded-lg border border-slate-300 bg-white p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500">
|
||||
<option value="baru">Baru</option>
|
||||
<option value="diproses">Diproses</option>
|
||||
<option value="selesai">Selesai</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Deskripsi Aduan</label>
|
||||
<textarea id="aduanDeskripsi" rows="3" placeholder="Jelaskan kondisi yang diadukan" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500"></textarea>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Tindak Lanjut</label>
|
||||
<textarea id="aduanTindakLanjut" rows="2" placeholder="Catatan respon / tindak lanjut" class="w-full rounded-lg border border-slate-300 p-2 text-sm outline-none focus:ring-2 focus:ring-rose-500"></textarea>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block text-[10px] font-bold uppercase tracking-widest text-slate-400">Foto (Opsional)</label>
|
||||
<input id="aduanFoto" type="file" accept="image/*" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2 text-[10px] text-slate-500 bg-slate-50 p-2 rounded">
|
||||
📍 Koordinat: <span id="aduanCoords">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex flex-wrap gap-2 border-t pt-4">
|
||||
<button id="saveAduanWargaBtn" class="rounded-lg bg-rose-600 px-4 py-2 text-sm font-bold text-white hover:bg-rose-700">Simpan Aduan</button>
|
||||
<button id="cancelAduanWargaBtn" 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>
|
||||
@@ -687,12 +829,20 @@
|
||||
let utilityControl = null;
|
||||
let rumahIbadahLayer = L.featureGroup().addTo(map);
|
||||
let pendudukMiskinLayer = L.featureGroup().addTo(map);
|
||||
let verifikasiLapanganLayer = L.featureGroup().addTo(map);
|
||||
let aduanWargaLayer = L.featureGroup().addTo(map);
|
||||
let rumahIbadahLayerMap = {};
|
||||
let pendudukMiskinLayerMap = {};
|
||||
let verifikasiLapanganLayerMap = {};
|
||||
let aduanWargaLayerMap = {};
|
||||
let rumahIbadahRecords = [];
|
||||
let pendudukMiskinRecords = [];
|
||||
let verifikasiLapanganRecords = [];
|
||||
let aduanWargaRecords = [];
|
||||
let pendingRumahIbadahCoords = null;
|
||||
let pendingPendudukMiskinCoords = null;
|
||||
let pendingVerifikasiCoords = null;
|
||||
let pendingAduanCoords = null;
|
||||
let editingRumahIbadahId = null;
|
||||
let selectedRumahIbadahId = null;
|
||||
let selectedBantuanRumahIbadahId = null;
|
||||
@@ -1151,7 +1301,29 @@
|
||||
longitude: Number(record.longitude)
|
||||
}));
|
||||
|
||||
return [...pointRecords, ...spatialRecords, ...roadDamageRegistryRecords, ...rumahIbadahRegistryRecords, ...pendudukMiskinRegistryRecords].sort((left, right) => {
|
||||
const verifikasiRegistryRecords = verifikasiLapanganRecords.map(record => ({
|
||||
id: Number(record.id),
|
||||
type: 'verifikasiLapangan',
|
||||
title: record.target_nama || 'Verifikasi lapangan',
|
||||
subtitle: record.petugas_nama || '-',
|
||||
status: record.status_verifikasi || 'menunggu',
|
||||
created_at: record.created_at || null,
|
||||
latitude: Number(record.latitude),
|
||||
longitude: Number(record.longitude)
|
||||
}));
|
||||
|
||||
const aduanRegistryRecords = aduanWargaRecords.map(record => ({
|
||||
id: Number(record.id),
|
||||
type: 'aduanWarga',
|
||||
title: record.kategori || 'Aduan warga',
|
||||
subtitle: record.pelapor_nama || '-',
|
||||
status: record.status_tindak_lanjut || 'baru',
|
||||
created_at: record.created_at || null,
|
||||
latitude: Number(record.latitude),
|
||||
longitude: Number(record.longitude)
|
||||
}));
|
||||
|
||||
return [...pointRecords, ...spatialRecords, ...roadDamageRegistryRecords, ...rumahIbadahRegistryRecords, ...pendudukMiskinRegistryRecords, ...verifikasiRegistryRecords, ...aduanRegistryRecords].sort((left, right) => {
|
||||
const leftDate = parseDateOnly(left.created_at);
|
||||
const rightDate = parseDateOnly(right.created_at);
|
||||
|
||||
@@ -1169,6 +1341,8 @@
|
||||
if (type === 'roadDamage') return 'Jalan Rusak';
|
||||
if (type === 'rumahIbadah') return 'Rumah Ibadah';
|
||||
if (type === 'pendudukMiskin') return 'Penduduk Miskin';
|
||||
if (type === 'verifikasiLapangan') return 'Verifikasi Lapangan';
|
||||
if (type === 'aduanWarga') return 'Aduan Warga';
|
||||
return 'Data';
|
||||
}
|
||||
|
||||
@@ -1179,6 +1353,8 @@
|
||||
if (type === 'roadDamage') return 'bg-red-100 text-red-700';
|
||||
if (type === 'rumahIbadah') return 'bg-teal-100 text-teal-700';
|
||||
if (type === 'pendudukMiskin') return 'bg-rose-100 text-rose-700';
|
||||
if (type === 'verifikasiLapangan') return 'bg-emerald-100 text-emerald-700';
|
||||
if (type === 'aduanWarga') return 'bg-fuchsia-100 text-fuchsia-700';
|
||||
return 'bg-slate-100 text-slate-700';
|
||||
}
|
||||
|
||||
@@ -1228,6 +1404,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'verifikasiLapangan') {
|
||||
const marker = verifikasiLapanganLayerMap[id];
|
||||
if (!marker) return;
|
||||
map.setView(marker.getLatLng(), Math.max(map.getZoom(), 16));
|
||||
marker.openPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'aduanWarga') {
|
||||
const marker = aduanWargaLayerMap[id];
|
||||
if (!marker) return;
|
||||
map.setView(marker.getLatLng(), Math.max(map.getZoom(), 16));
|
||||
marker.openPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
const layer = featureLayerMap[id];
|
||||
if (!layer) return;
|
||||
if (layer.getBounds) {
|
||||
@@ -1267,7 +1459,11 @@
|
||||
? `PIC: ${record.subtitle}`
|
||||
: record.type === 'pendudukMiskin'
|
||||
? `Anggota: ${record.subtitle}`
|
||||
: `Kategori: ${record.subtitle}`;
|
||||
: record.type === 'verifikasiLapangan'
|
||||
? `Petugas: ${record.subtitle}`
|
||||
: record.type === 'aduanWarga'
|
||||
? `Pelapor: ${record.subtitle}`
|
||||
: `Kategori: ${record.subtitle}`;
|
||||
const statusLabel = record.type === 'point'
|
||||
? `24 Jam: ${record.status}`
|
||||
: record.type === 'roadDamage'
|
||||
@@ -1276,7 +1472,11 @@
|
||||
? `Radius: ${record.status}`
|
||||
: record.type === 'pendudukMiskin'
|
||||
? `Rumah Ibadah: ${record.status}`
|
||||
: `Status: ${record.status}`;
|
||||
: record.type === 'verifikasiLapangan'
|
||||
? `Status Verifikasi: ${record.status}`
|
||||
: record.type === 'aduanWarga'
|
||||
? `Status Tindak Lanjut: ${record.status}`
|
||||
: `Status: ${record.status}`;
|
||||
|
||||
return `
|
||||
<button type="button" onclick="focusRegistryItem('${record.type}', ${record.id})" class="registry-item w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-left shadow-sm">
|
||||
@@ -1342,6 +1542,8 @@
|
||||
roadDamageLayer: document.getElementById('layerToggle-roadDamageLayer'),
|
||||
rumahIbadahLayer: document.getElementById('layerToggle-rumahIbadahLayer'),
|
||||
pendudukMiskinLayer: document.getElementById('layerToggle-pendudukMiskinLayer'),
|
||||
verifikasiLapanganLayer: document.getElementById('layerToggle-verifikasiLapanganLayer'),
|
||||
aduanWargaLayer: document.getElementById('layerToggle-aduanWargaLayer'),
|
||||
roadNetworkLayer: document.getElementById('layerToggle-roadNetworkLayer')
|
||||
}
|
||||
};
|
||||
@@ -1356,6 +1558,8 @@
|
||||
{ key: 'roadDamageLayer', layer: roadDamageLayer, label: 'Jalan Rusak' },
|
||||
{ key: 'rumahIbadahLayer', layer: rumahIbadahLayer, label: 'Rumah Ibadah' },
|
||||
{ key: 'pendudukMiskinLayer', layer: pendudukMiskinLayer, label: 'Penduduk Miskin' },
|
||||
{ key: 'verifikasiLapanganLayer', layer: verifikasiLapanganLayer, label: 'Verifikasi Lapangan' },
|
||||
{ key: 'aduanWargaLayer', layer: aduanWargaLayer, label: 'Aduan Warga' },
|
||||
{ key: 'roadNetworkLayer', layer: roadNetworkLayer, label: 'Jalan Eksternal' }
|
||||
];
|
||||
}
|
||||
@@ -1503,6 +1707,11 @@
|
||||
const ui = getBantuanUi();
|
||||
if (!ui.panel || !ui.list || !ui.count || !ui.rumahFilter || !ui.statusFilter || !ui.search) return;
|
||||
|
||||
const existingSummary = ui.panel.querySelector('[data-bantuan-summary="1"]');
|
||||
if (existingSummary) {
|
||||
existingSummary.remove();
|
||||
}
|
||||
|
||||
const previousValue = ui.rumahFilter.value;
|
||||
const options = ['<option value="">Pilih rumah ibadah...</option>'].concat(
|
||||
rumahIbadahRecords.map(record => `<option value="${Number(record.id)}">${record.nama} (radius ${Number(record.radius_m || 0).toLocaleString('id-ID')} m)</option>`)
|
||||
@@ -1545,8 +1754,34 @@
|
||||
return true;
|
||||
});
|
||||
|
||||
const statusCounts = residents.reduce((accumulator, item) => {
|
||||
const status = normalizeAidStatus(item.bantuan_status);
|
||||
accumulator[status] = (accumulator[status] || 0) + 1;
|
||||
return accumulator;
|
||||
}, { belum: 0, proses: 0, sudah: 0 });
|
||||
|
||||
ui.count.innerText = String(residents.length);
|
||||
|
||||
const summaryWrapper = document.createElement('div');
|
||||
summaryWrapper.setAttribute('data-bantuan-summary', '1');
|
||||
summaryWrapper.className = 'mt-3 grid grid-cols-3 gap-2 text-[11px] text-slate-600';
|
||||
summaryWrapper.innerHTML = `
|
||||
<div class="rounded-lg bg-rose-50 px-2 py-2 text-center">
|
||||
<div class="font-black text-rose-700">${statusCounts.belum || 0}</div>
|
||||
<div>Belum</div>
|
||||
</div>
|
||||
<div class="rounded-lg bg-amber-50 px-2 py-2 text-center">
|
||||
<div class="font-black text-amber-700">${statusCounts.proses || 0}</div>
|
||||
<div>Diproses</div>
|
||||
</div>
|
||||
<div class="rounded-lg bg-emerald-50 px-2 py-2 text-center">
|
||||
<div class="font-black text-emerald-700">${statusCounts.sudah || 0}</div>
|
||||
<div>Sudah</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
ui.list.parentNode.insertBefore(summaryWrapper, ui.list);
|
||||
|
||||
if (!residents.length) {
|
||||
ui.list.innerHTML = `
|
||||
<div class="rounded-xl border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-sm text-slate-500">
|
||||
@@ -1681,11 +1916,15 @@
|
||||
pendudukMiskin: document.getElementById('modePendudukMiskinBtn'),
|
||||
choropleth: document.getElementById('modeChoroplethBtn'),
|
||||
roadDamage: document.getElementById('leafletRoadDamageBtn') || document.getElementById('modeRoadDamageBtn'),
|
||||
verifikasiLapangan: document.getElementById('leafletVerifikasiBtn') || document.getElementById('modeVerifikasiBtn'),
|
||||
aduanWarga: document.getElementById('leafletAduanWargaBtn') || document.getElementById('modeAduanWargaBtn'),
|
||||
miniPoint: document.getElementById('miniModePointBtn'),
|
||||
miniPolyline: document.getElementById('miniModePolylineBtn'),
|
||||
miniPolygon: document.getElementById('miniModePolygonBtn'),
|
||||
miniRumahIbadah: document.getElementById('miniModeRumahIbadahBtn'),
|
||||
miniPendudukMiskin: document.getElementById('miniModePendudukMiskinBtn')
|
||||
miniPendudukMiskin: document.getElementById('miniModePendudukMiskinBtn'),
|
||||
miniVerifikasiLapangan: document.getElementById('miniModeVerifikasiBtn'),
|
||||
miniAduanWarga: document.getElementById('miniModeAduanWargaBtn')
|
||||
};
|
||||
|
||||
Object.values(modeButtons).forEach(button => {
|
||||
@@ -1711,6 +1950,14 @@
|
||||
if (modeButtons.pendudukMiskin) modeButtons.pendudukMiskin.classList.add('active');
|
||||
if (modeButtons.miniPendudukMiskin) modeButtons.miniPendudukMiskin.classList.add('active');
|
||||
}
|
||||
if (mode === 'verifikasiLapangan') {
|
||||
if (modeButtons.verifikasiLapangan) modeButtons.verifikasiLapangan.classList.add('active');
|
||||
if (modeButtons.miniVerifikasiLapangan) modeButtons.miniVerifikasiLapangan.classList.add('active');
|
||||
}
|
||||
if (mode === 'aduanWarga') {
|
||||
if (modeButtons.aduanWarga) modeButtons.aduanWarga.classList.add('active');
|
||||
if (modeButtons.miniAduanWarga) modeButtons.miniAduanWarga.classList.add('active');
|
||||
}
|
||||
if (modeButtons[mode]) {
|
||||
modeButtons[mode].classList.add('active');
|
||||
}
|
||||
@@ -1737,14 +1984,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'roadDamage') {
|
||||
if (mode === 'roadDamage' || mode === 'verifikasiLapangan' || mode === 'aduanWarga') {
|
||||
spatialControls.classList.add('hidden');
|
||||
runActionBtn.innerText = 'Klik Peta';
|
||||
runActionBtn.disabled = true;
|
||||
clearDraftBtn.disabled = true;
|
||||
updateDrawControl(null);
|
||||
document.getElementById('modeMeasure').value = '';
|
||||
loadRoadDamage(true);
|
||||
if (mode === 'roadDamage') loadRoadDamage(true);
|
||||
if (mode === 'verifikasiLapangan') loadVerifikasiLapangan(true);
|
||||
if (mode === 'aduanWarga') loadAduanWarga(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2376,6 +2625,10 @@
|
||||
<button onclick="deletePendudukMiskin(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
<button onclick="focusRegistryItem('rumahIbadah', ${record.rumah_ibadah_id || 0})" class="rounded-lg bg-emerald-50 px-3 py-2 text-xs font-bold text-emerald-700 hover:bg-emerald-100">Lihat Rumah Ibadah</button>
|
||||
</div>
|
||||
<div class="mt-2 grid grid-cols-2 gap-2">
|
||||
<button onclick="openVerifikasiFromPenduduk(${record.id})" class="rounded-lg bg-emerald-50 px-3 py-2 text-xs font-bold text-emerald-700 hover:bg-emerald-100">Verifikasi</button>
|
||||
<button onclick="setDataMode('aduanWarga'); openAduanWargaModal(${Number(record.latitude)}, ${Number(record.longitude)})" class="rounded-lg bg-fuchsia-50 px-3 py-2 text-xs font-bold text-fuchsia-700 hover:bg-fuchsia-100">Buat Aduan</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -2459,6 +2712,329 @@
|
||||
showMessage(`Menampilkan ${withinRadius.length} lokasi miskin terdekat`, 'bg-emerald-600 text-white');
|
||||
}
|
||||
|
||||
function getAidStatusColor(status) {
|
||||
const normalized = normalizeAidStatus(status);
|
||||
if (normalized === 'sudah') return '#10b981';
|
||||
if (normalized === 'proses') return '#f59e0b';
|
||||
return '#dc2626';
|
||||
}
|
||||
|
||||
function createPendudukMiskinIcon(status) {
|
||||
const color = getAidStatusColor(status);
|
||||
return L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="8" r="4"/><path d="M4 22a8 8 0 0 1 16 0"/></svg>`,
|
||||
iconSize: [32, 40],
|
||||
iconAnchor: [16, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function getVerifikasiStatusBadge(status) {
|
||||
const value = String(status || 'menunggu').toLowerCase().trim();
|
||||
if (value === 'valid') return { label: 'Valid', className: 'bg-emerald-100 text-emerald-700' };
|
||||
if (value === 'perlu_tindak_lanjut') return { label: 'Perlu Tindak Lanjut', className: 'bg-amber-100 text-amber-700' };
|
||||
return { label: 'Menunggu', className: 'bg-slate-100 text-slate-700' };
|
||||
}
|
||||
|
||||
function getVerifikasiIcon(status) {
|
||||
const value = String(status || 'menunggu').toLowerCase().trim();
|
||||
const color = value === 'valid' ? '#10b981' : value === 'perlu_tindak_lanjut' ? '#f59e0b' : '#64748b';
|
||||
return L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><path d="M12 0 3 4v6c0 6.2 4.2 11.4 9 14 4.8-2.6 9-7.8 9-14V4z"/><path d="M9.5 11.5 11 13l3.5-4" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
|
||||
iconSize: [32, 40],
|
||||
iconAnchor: [16, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function getAduanStatusBadge(status) {
|
||||
const value = String(status || 'baru').toLowerCase().trim();
|
||||
if (value === 'selesai') return { label: 'Selesai', className: 'bg-emerald-100 text-emerald-700' };
|
||||
if (value === 'diproses') return { label: 'Diproses', className: 'bg-amber-100 text-amber-700' };
|
||||
return { label: 'Baru', className: 'bg-rose-100 text-rose-700' };
|
||||
}
|
||||
|
||||
function getAduanIcon(status) {
|
||||
const value = String(status || 'baru').toLowerCase().trim();
|
||||
const color = value === 'selesai' ? '#10b981' : value === 'diproses' ? '#f59e0b' : '#e11d48';
|
||||
return L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="${color}" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-5 0-9 3.6-9 8 0 3.1 2.1 5.8 5.2 7l-.7 4 4.5-2.4c.3 0 .6.1 1 .1 5 0 9-3.6 9-8s-4-8.7-9-8.7z"/><circle cx="9" cy="10" r="1" fill="white"/><circle cx="12" cy="10" r="1" fill="white"/><circle cx="15" cy="10" r="1" fill="white"/></svg>`,
|
||||
iconSize: [32, 40],
|
||||
iconAnchor: [16, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function buildVerifikasiLapanganPopup(record) {
|
||||
const badge = getVerifikasiStatusBadge(record.status_verifikasi);
|
||||
const fotoHtml = record.foto ? `<img src="${record.foto}" alt="Foto verifikasi" class="mt-2 w-full rounded-lg max-h-48 object-cover">` : '';
|
||||
|
||||
return `
|
||||
<div class="w-full p-4">
|
||||
<h3 class="text-lg font-bold text-slate-800">Verifikasi Lapangan</h3>
|
||||
<p class="mt-1 text-sm text-slate-600">Target: ${record.target_nama || '-'} (${record.jenis_target || '-'})</p>
|
||||
<p class="mt-1 text-sm text-slate-600">Petugas: ${record.petugas_nama || '-'}</p>
|
||||
<div class="mt-2">
|
||||
<span class="registry-chip ${badge.className}">${badge.label}</span>
|
||||
</div>
|
||||
${record.hasil_temuan ? `<p class="mt-2 text-sm text-slate-600"><strong>Temuan:</strong> ${record.hasil_temuan}</p>` : ''}
|
||||
${record.tindak_lanjut ? `<p class="mt-1 text-sm text-slate-600"><strong>Tindak lanjut:</strong> ${record.tindak_lanjut}</p>` : ''}
|
||||
<p class="mt-1 text-xs text-slate-500">📍 ${Number(record.latitude).toFixed(5)}, ${Number(record.longitude).toFixed(5)}</p>
|
||||
${fotoHtml}
|
||||
<div class="mt-3 grid grid-cols-2 gap-2 border-t pt-3">
|
||||
<button onclick="openVerifikasiLapanganEditor(${record.id})" class="rounded-lg bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700 hover:bg-indigo-100">Edit</button>
|
||||
<button onclick="deleteVerifikasiLapangan(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function openVerifikasiLapanganModal(lat, lng, record = null) {
|
||||
pendingVerifikasiCoords = { lat, lng };
|
||||
document.getElementById('verifikasiLapanganId').value = record ? String(record.id) : '';
|
||||
document.getElementById('verifikasiJenisTarget').value = record ? (record.jenis_target || 'penduduk_miskin') : 'penduduk_miskin';
|
||||
document.getElementById('verifikasiTargetNama').value = record ? (record.target_nama || '') : '';
|
||||
document.getElementById('verifikasiPetugasNama').value = record ? (record.petugas_nama || '') : '';
|
||||
document.getElementById('verifikasiStatus').value = record ? (record.status_verifikasi || 'menunggu') : 'menunggu';
|
||||
document.getElementById('verifikasiHasilTemuan').value = record ? (record.hasil_temuan || '') : '';
|
||||
document.getElementById('verifikasiTindakLanjut').value = record ? (record.tindak_lanjut || '') : '';
|
||||
document.getElementById('verifikasiCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||
document.getElementById('verifikasiFoto').value = '';
|
||||
|
||||
const modal = document.getElementById('verifikasiLapanganModal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
suppressAutoReload = true;
|
||||
}
|
||||
|
||||
function closeVerifikasiLapanganModal() {
|
||||
const modal = document.getElementById('verifikasiLapanganModal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
pendingVerifikasiCoords = null;
|
||||
suppressAutoReload = !!document.querySelector('[id^="edit-mode-"]:not(.hidden)') || !!editingFeatureId;
|
||||
}
|
||||
|
||||
function saveVerifikasiLapangan() {
|
||||
if (!pendingVerifikasiCoords) {
|
||||
showMessage('Koordinat verifikasi tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = document.getElementById('verifikasiLapanganId').value;
|
||||
const formData = new FormData();
|
||||
formData.append('jenis_target', document.getElementById('verifikasiJenisTarget').value);
|
||||
formData.append('target_nama', document.getElementById('verifikasiTargetNama').value.trim());
|
||||
formData.append('petugas_nama', document.getElementById('verifikasiPetugasNama').value.trim());
|
||||
formData.append('status_verifikasi', document.getElementById('verifikasiStatus').value);
|
||||
formData.append('hasil_temuan', document.getElementById('verifikasiHasilTemuan').value.trim());
|
||||
formData.append('tindak_lanjut', document.getElementById('verifikasiTindakLanjut').value.trim());
|
||||
formData.append('latitude', pendingVerifikasiCoords.lat);
|
||||
formData.append('longitude', pendingVerifikasiCoords.lng);
|
||||
|
||||
const fileInput = document.getElementById('verifikasiFoto');
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
formData.append('foto', fileInput.files[0]);
|
||||
}
|
||||
|
||||
const endpoint = id ? 'update_verifikasi_lapangan.php' : 'save_verifikasi_lapangan.php';
|
||||
if (id) {
|
||||
formData.append('id', id);
|
||||
}
|
||||
|
||||
fetch(endpoint, { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, endpoint))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('✅ Verifikasi lapangan berhasil disimpan', 'bg-green-600 text-white');
|
||||
closeVerifikasiLapanganModal();
|
||||
loadVerifikasiLapangan(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menyimpan verifikasi');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menyimpan verifikasi', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function deleteVerifikasiLapangan(id) {
|
||||
if (!confirm('Hapus verifikasi lapangan ini?')) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
|
||||
fetch('delete_verifikasi_lapangan.php', { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, 'delete_verifikasi_lapangan.php'))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('🗑️ Verifikasi lapangan berhasil dihapus', 'bg-orange-600 text-white');
|
||||
loadVerifikasiLapangan(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menghapus verifikasi');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menghapus verifikasi', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function openVerifikasiLapanganEditor(id) {
|
||||
const record = verifikasiLapanganRecords.find(item => Number(item.id) === Number(id));
|
||||
if (!record) {
|
||||
showMessage('Data verifikasi tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
openVerifikasiLapanganModal(Number(record.latitude), Number(record.longitude), record);
|
||||
}
|
||||
|
||||
function openVerifikasiFromPenduduk(id) {
|
||||
const record = pendudukMiskinRecords.find(item => Number(item.id) === Number(id));
|
||||
if (!record) {
|
||||
showMessage('Data penduduk miskin tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
openVerifikasiLapanganModal(Number(record.latitude), Number(record.longitude), {
|
||||
jenis_target: 'penduduk_miskin',
|
||||
target_nama: record.nama_ketua_kk || '',
|
||||
petugas_nama: '',
|
||||
status_verifikasi: 'menunggu',
|
||||
hasil_temuan: '',
|
||||
tindak_lanjut: ''
|
||||
});
|
||||
}
|
||||
|
||||
function buildAduanWargaPopup(record) {
|
||||
const badge = getAduanStatusBadge(record.status_tindak_lanjut);
|
||||
const fotoHtml = record.foto ? `<img src="${record.foto}" alt="Foto aduan" class="mt-2 w-full rounded-lg max-h-48 object-cover">` : '';
|
||||
|
||||
return `
|
||||
<div class="w-full p-4">
|
||||
<h3 class="text-lg font-bold text-slate-800">Aduan Warga</h3>
|
||||
<p class="mt-1 text-sm text-slate-600">Kategori: ${record.kategori || '-'}</p>
|
||||
<p class="mt-1 text-sm text-slate-600">Pelapor: ${record.pelapor_nama || '-'}</p>
|
||||
<p class="mt-1 text-sm text-slate-600">Kontak: ${record.kontak_pelapor || '-'}</p>
|
||||
<div class="mt-2">
|
||||
<span class="registry-chip ${badge.className}">${badge.label}</span>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-slate-600"><strong>Deskripsi:</strong> ${record.deskripsi || '-'}</p>
|
||||
${record.tindak_lanjut ? `<p class="mt-1 text-sm text-slate-600"><strong>Tindak lanjut:</strong> ${record.tindak_lanjut}</p>` : ''}
|
||||
<p class="mt-1 text-xs text-slate-500">📍 ${Number(record.latitude).toFixed(5)}, ${Number(record.longitude).toFixed(5)}</p>
|
||||
${fotoHtml}
|
||||
<div class="mt-3 grid grid-cols-2 gap-2 border-t pt-3">
|
||||
<button onclick="openAduanWargaEditor(${record.id})" class="rounded-lg bg-indigo-50 px-3 py-2 text-xs font-bold text-indigo-700 hover:bg-indigo-100">Edit</button>
|
||||
<button onclick="deleteAduanWarga(${record.id})" class="rounded-lg bg-red-50 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-100">Hapus</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function openAduanWargaModal(lat, lng, record = null) {
|
||||
pendingAduanCoords = { lat, lng };
|
||||
document.getElementById('aduanWargaId').value = record ? String(record.id) : '';
|
||||
document.getElementById('aduanKategori').value = record ? (record.kategori || '') : '';
|
||||
document.getElementById('aduanPelaporNama').value = record ? (record.pelapor_nama || '') : '';
|
||||
document.getElementById('aduanKontakPelapor').value = record ? (record.kontak_pelapor || '') : '';
|
||||
document.getElementById('aduanStatus').value = record ? (record.status_tindak_lanjut || 'baru') : 'baru';
|
||||
document.getElementById('aduanDeskripsi').value = record ? (record.deskripsi || '') : '';
|
||||
document.getElementById('aduanTindakLanjut').value = record ? (record.tindak_lanjut || '') : '';
|
||||
document.getElementById('aduanCoords').innerText = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
||||
document.getElementById('aduanFoto').value = '';
|
||||
|
||||
const modal = document.getElementById('aduanWargaModal');
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
suppressAutoReload = true;
|
||||
}
|
||||
|
||||
function closeAduanWargaModal() {
|
||||
const modal = document.getElementById('aduanWargaModal');
|
||||
modal.classList.add('hidden');
|
||||
modal.classList.remove('flex');
|
||||
pendingAduanCoords = null;
|
||||
suppressAutoReload = !!document.querySelector('[id^="edit-mode-"]:not(.hidden)') || !!editingFeatureId;
|
||||
}
|
||||
|
||||
function saveAduanWarga() {
|
||||
if (!pendingAduanCoords) {
|
||||
showMessage('Koordinat aduan tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = document.getElementById('aduanWargaId').value;
|
||||
const kategori = document.getElementById('aduanKategori').value.trim();
|
||||
const pelaporNama = document.getElementById('aduanPelaporNama').value.trim();
|
||||
const deskripsi = document.getElementById('aduanDeskripsi').value.trim();
|
||||
|
||||
if (!kategori || !pelaporNama || !deskripsi) {
|
||||
showMessage('Kategori, nama pelapor, dan deskripsi wajib diisi', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('kategori', kategori);
|
||||
formData.append('pelapor_nama', pelaporNama);
|
||||
formData.append('kontak_pelapor', document.getElementById('aduanKontakPelapor').value.trim());
|
||||
formData.append('deskripsi', deskripsi);
|
||||
formData.append('status_tindak_lanjut', document.getElementById('aduanStatus').value);
|
||||
formData.append('tindak_lanjut', document.getElementById('aduanTindakLanjut').value.trim());
|
||||
formData.append('latitude', pendingAduanCoords.lat);
|
||||
formData.append('longitude', pendingAduanCoords.lng);
|
||||
|
||||
const fileInput = document.getElementById('aduanFoto');
|
||||
if (fileInput.files && fileInput.files[0]) {
|
||||
formData.append('foto', fileInput.files[0]);
|
||||
}
|
||||
|
||||
const endpoint = id ? 'update_aduan_warga.php' : 'save_aduan_warga.php';
|
||||
if (id) {
|
||||
formData.append('id', id);
|
||||
}
|
||||
|
||||
fetch(endpoint, { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, endpoint))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('✅ Aduan warga berhasil disimpan', 'bg-green-600 text-white');
|
||||
closeAduanWargaModal();
|
||||
loadAduanWarga(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menyimpan aduan');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menyimpan aduan', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function deleteAduanWarga(id) {
|
||||
if (!confirm('Hapus aduan warga ini?')) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
|
||||
fetch('delete_aduan_warga.php', { method: 'POST', body: formData })
|
||||
.then(res => parseJsonResponse(res, 'delete_aduan_warga.php'))
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showMessage('🗑️ Aduan warga berhasil dihapus', 'bg-orange-600 text-white');
|
||||
loadAduanWarga(true);
|
||||
} else {
|
||||
throw new Error(data.message || 'Gagal menghapus aduan');
|
||||
}
|
||||
})
|
||||
.catch(err => showMessage(err.message || 'Gagal menghapus aduan', 'bg-red-600 text-white'));
|
||||
}
|
||||
|
||||
function openAduanWargaEditor(id) {
|
||||
const record = aduanWargaRecords.find(item => Number(item.id) === Number(id));
|
||||
if (!record) {
|
||||
showMessage('Data aduan tidak ditemukan', 'bg-red-600 text-white');
|
||||
return;
|
||||
}
|
||||
|
||||
openAduanWargaModal(Number(record.latitude), Number(record.longitude), record);
|
||||
}
|
||||
|
||||
function saveRumahIbadah() {
|
||||
if (!pendingRumahIbadahCoords) {
|
||||
showMessage('Koordinat rumah ibadah tidak ditemukan', 'bg-red-600 text-white');
|
||||
@@ -2608,16 +3184,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function createPendudukMiskinIcon() {
|
||||
return L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<svg width="32" height="40" viewBox="0 0 24 24" fill="#b91c1c" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="8" r="4"/><path d="M4 22a8 8 0 0 1 16 0"/></svg>`,
|
||||
iconSize: [32, 40],
|
||||
iconAnchor: [16, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
}
|
||||
|
||||
function clearRumahIbadahLayers() {
|
||||
rumahIbadahLayer.clearLayers();
|
||||
rumahIbadahLayerMap = {};
|
||||
@@ -2700,7 +3266,7 @@
|
||||
|
||||
data.forEach(record => {
|
||||
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||
icon: createPendudukMiskinIcon()
|
||||
icon: createPendudukMiskinIcon(record.bantuan_status)
|
||||
}).addTo(pendudukMiskinLayer);
|
||||
|
||||
marker.bindPopup(buildPendudukMiskinPopup(record));
|
||||
@@ -2718,6 +3284,86 @@
|
||||
.catch(err => console.error('Gagal memuat penduduk miskin:', err));
|
||||
}
|
||||
|
||||
function getVerifikasiLayerPopup(record) {
|
||||
return buildVerifikasiLapanganPopup(record);
|
||||
}
|
||||
|
||||
function getAduanLayerPopup(record) {
|
||||
return buildAduanWargaPopup(record);
|
||||
}
|
||||
|
||||
function clearVerifikasiLapanganLayers() {
|
||||
verifikasiLapanganLayer.clearLayers();
|
||||
verifikasiLapanganLayerMap = {};
|
||||
}
|
||||
|
||||
function clearAduanWargaLayers() {
|
||||
aduanWargaLayer.clearLayers();
|
||||
aduanWargaLayerMap = {};
|
||||
}
|
||||
|
||||
function loadVerifikasiLapangan(force = false) {
|
||||
if (!force && (document.hidden || suppressAutoReload)) return;
|
||||
|
||||
fetch('get_verifikasi_lapangan.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 verifikasi tidak valid');
|
||||
}
|
||||
|
||||
verifikasiLapanganRecords = data;
|
||||
clearVerifikasiLapanganLayers();
|
||||
|
||||
data.forEach(record => {
|
||||
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||
icon: getVerifikasiIcon(record.status_verifikasi)
|
||||
}).addTo(verifikasiLapanganLayer);
|
||||
|
||||
marker.bindPopup(getVerifikasiLayerPopup(record));
|
||||
verifikasiLapanganLayerMap[record.id] = marker;
|
||||
});
|
||||
|
||||
renderInputRegistry();
|
||||
})
|
||||
.catch(err => console.error('Gagal memuat verifikasi lapangan:', err));
|
||||
}
|
||||
|
||||
function loadAduanWarga(force = false) {
|
||||
if (!force && (document.hidden || suppressAutoReload)) return;
|
||||
|
||||
fetch('get_aduan_warga.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 aduan tidak valid');
|
||||
}
|
||||
|
||||
aduanWargaRecords = data;
|
||||
clearAduanWargaLayers();
|
||||
|
||||
data.forEach(record => {
|
||||
const marker = L.marker([Number(record.latitude), Number(record.longitude)], {
|
||||
icon: getAduanIcon(record.status_tindak_lanjut)
|
||||
}).addTo(aduanWargaLayer);
|
||||
|
||||
marker.bindPopup(getAduanLayerPopup(record));
|
||||
aduanWargaLayerMap[record.id] = marker;
|
||||
});
|
||||
|
||||
renderInputRegistry();
|
||||
})
|
||||
.catch(err => console.error('Gagal memuat aduan warga:', err));
|
||||
}
|
||||
|
||||
function convertCircleToPolygon(circleLayer, vertices = 48) {
|
||||
const center = circleLayer.getLatLng();
|
||||
const radius = Number(circleLayer.getRadius() || 0);
|
||||
@@ -3396,7 +4042,9 @@
|
||||
'Polygon': polygonLayerGroup,
|
||||
'Jalan Rusak': roadDamageLayer,
|
||||
'Rumah Ibadah': rumahIbadahLayer,
|
||||
'Penduduk Miskin': pendudukMiskinLayer
|
||||
'Penduduk Miskin': pendudukMiskinLayer,
|
||||
'Verifikasi Lapangan': verifikasiLapanganLayer,
|
||||
'Aduan Warga': aduanWargaLayer
|
||||
}, {
|
||||
collapsed: true,
|
||||
position: 'topleft'
|
||||
@@ -3415,6 +4063,8 @@
|
||||
pendudukMiskinBtn.classList.toggle('active', activeMode === 'pendudukMiskin');
|
||||
choroplethBtn.classList.toggle('active', activeMode === 'choropleth');
|
||||
roadDamageBtn.classList.toggle('active', activeMode === 'roadDamage');
|
||||
verifikasiBtn.classList.toggle('active', activeMode === 'verifikasiLapangan');
|
||||
aduanWargaBtn.classList.toggle('active', activeMode === 'aduanWarga');
|
||||
}
|
||||
|
||||
const registryBtn = L.DomUtil.create('a', '', container);
|
||||
@@ -3459,6 +4109,18 @@
|
||||
roadDamageBtn.title = 'Lapor Jalan Rusak';
|
||||
roadDamageBtn.innerHTML = 'Jr';
|
||||
|
||||
const verifikasiBtn = L.DomUtil.create('a', '', container);
|
||||
verifikasiBtn.href = '#';
|
||||
verifikasiBtn.id = 'leafletVerifikasiBtn';
|
||||
verifikasiBtn.title = 'Verifikasi Lapangan';
|
||||
verifikasiBtn.innerHTML = 'Vf';
|
||||
|
||||
const aduanWargaBtn = L.DomUtil.create('a', '', container);
|
||||
aduanWargaBtn.href = '#';
|
||||
aduanWargaBtn.id = 'leafletAduanWargaBtn';
|
||||
aduanWargaBtn.title = 'Aduan Warga';
|
||||
aduanWargaBtn.innerHTML = 'Ad';
|
||||
|
||||
L.DomEvent.disableClickPropagation(container);
|
||||
|
||||
L.DomEvent.on(registryBtn, 'click', function(event) {
|
||||
@@ -3508,6 +4170,22 @@
|
||||
showMessage('Mode laporan jalan rusak aktif. Klik peta untuk menandai lokasi.', 'bg-red-600 text-white');
|
||||
});
|
||||
|
||||
L.DomEvent.on(verifikasiBtn, 'click', function(event) {
|
||||
L.DomEvent.stop(event);
|
||||
setDataMode('verifikasiLapangan');
|
||||
toggleInputRegistry(false);
|
||||
setUtilityModeActive('verifikasiLapangan');
|
||||
showMessage('Mode verifikasi lapangan aktif. Klik peta untuk mencatat hasil cek.', 'bg-emerald-600 text-white');
|
||||
});
|
||||
|
||||
L.DomEvent.on(aduanWargaBtn, 'click', function(event) {
|
||||
L.DomEvent.stop(event);
|
||||
setDataMode('aduanWarga');
|
||||
toggleInputRegistry(false);
|
||||
setUtilityModeActive('aduanWarga');
|
||||
showMessage('Mode aduan warga aktif. Klik peta untuk mencatat laporan.', 'bg-fuchsia-600 text-white');
|
||||
});
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
@@ -3740,6 +4418,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (appMode === 'verifikasiLapangan') {
|
||||
openVerifikasiLapanganModal(e.latlng.lat, e.latlng.lng);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appMode === 'aduanWarga') {
|
||||
openAduanWargaModal(e.latlng.lat, e.latlng.lng);
|
||||
return;
|
||||
}
|
||||
|
||||
if (appMode === 'rumahIbadah') {
|
||||
openRumahIbadahModal(e.latlng.lat, e.latlng.lng);
|
||||
return;
|
||||
@@ -3788,6 +4476,8 @@
|
||||
loadFeatures();
|
||||
loadRumahIbadah();
|
||||
loadPendudukMiskin();
|
||||
loadVerifikasiLapangan();
|
||||
loadAduanWarga();
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@@ -3797,6 +4487,8 @@
|
||||
loadFeatures(true);
|
||||
loadRumahIbadah(true);
|
||||
loadPendudukMiskin(true);
|
||||
loadVerifikasiLapangan(true);
|
||||
loadAduanWarga(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3841,6 +4533,8 @@
|
||||
loadRumahIbadah(true);
|
||||
loadPendudukMiskin(true);
|
||||
loadRoadDamage(true);
|
||||
loadVerifikasiLapangan(true);
|
||||
loadAduanWarga(true);
|
||||
loadExternalRoadNetwork(true);
|
||||
startAutoRefresh();
|
||||
};
|
||||
@@ -3859,6 +4553,14 @@
|
||||
document.getElementById('modePendudukMiskinBtn').addEventListener('click', () => setDataMode('pendudukMiskin'));
|
||||
document.getElementById('modeChoroplethBtn').addEventListener('click', () => setDataMode('choropleth'));
|
||||
document.getElementById('modeRoadDamageBtn').addEventListener('click', () => setDataMode('roadDamage'));
|
||||
const modeVerifikasiBtn = document.getElementById('modeVerifikasiBtn');
|
||||
if (modeVerifikasiBtn) {
|
||||
modeVerifikasiBtn.addEventListener('click', () => setDataMode('verifikasiLapangan'));
|
||||
}
|
||||
const modeAduanWargaBtn = document.getElementById('modeAduanWargaBtn');
|
||||
if (modeAduanWargaBtn) {
|
||||
modeAduanWargaBtn.addEventListener('click', () => setDataMode('aduanWarga'));
|
||||
}
|
||||
const miniModePointBtn = document.getElementById('miniModePointBtn');
|
||||
if (miniModePointBtn) {
|
||||
miniModePointBtn.addEventListener('click', () => {
|
||||
@@ -3894,6 +4596,20 @@
|
||||
showMessage('Klik peta untuk menambah penduduk miskin', 'bg-indigo-600 text-white');
|
||||
});
|
||||
}
|
||||
const miniModeVerifikasiBtn = document.getElementById('miniModeVerifikasiBtn');
|
||||
if (miniModeVerifikasiBtn) {
|
||||
miniModeVerifikasiBtn.addEventListener('click', () => {
|
||||
setDataMode('verifikasiLapangan');
|
||||
showMessage('Klik peta untuk verifikasi lapangan', 'bg-emerald-600 text-white');
|
||||
});
|
||||
}
|
||||
const miniModeAduanWargaBtn = document.getElementById('miniModeAduanWargaBtn');
|
||||
if (miniModeAduanWargaBtn) {
|
||||
miniModeAduanWargaBtn.addEventListener('click', () => {
|
||||
setDataMode('aduanWarga');
|
||||
showMessage('Klik peta untuk membuat aduan warga', 'bg-fuchsia-600 text-white');
|
||||
});
|
||||
}
|
||||
const toggleRegistryDrawerBtn = document.getElementById('toggleRegistryDrawerBtn');
|
||||
if (toggleRegistryDrawerBtn) {
|
||||
toggleRegistryDrawerBtn.addEventListener('click', () => toggleInputRegistry());
|
||||
@@ -4033,6 +4749,20 @@
|
||||
if (event.target.id === 'pendudukMiskinModal') closePendudukMiskinModal();
|
||||
});
|
||||
|
||||
document.getElementById('saveVerifikasiLapanganBtn').addEventListener('click', saveVerifikasiLapangan);
|
||||
document.getElementById('cancelVerifikasiLapanganBtn').addEventListener('click', closeVerifikasiLapanganModal);
|
||||
document.getElementById('closeVerifikasiLapanganModalBtn').addEventListener('click', closeVerifikasiLapanganModal);
|
||||
document.getElementById('verifikasiLapanganModal').addEventListener('click', (event) => {
|
||||
if (event.target.id === 'verifikasiLapanganModal') closeVerifikasiLapanganModal();
|
||||
});
|
||||
|
||||
document.getElementById('saveAduanWargaBtn').addEventListener('click', saveAduanWarga);
|
||||
document.getElementById('cancelAduanWargaBtn').addEventListener('click', closeAduanWargaModal);
|
||||
document.getElementById('closeAduanWargaModalBtn').addEventListener('click', closeAduanWargaModal);
|
||||
document.getElementById('aduanWargaModal').addEventListener('click', (event) => {
|
||||
if (event.target.id === 'aduanWargaModal') closeAduanWargaModal();
|
||||
});
|
||||
|
||||
// Road Damage Modal Event Listeners
|
||||
document.getElementById('saveRoadDamageBtn').addEventListener('click', saveRoadDamage);
|
||||
document.getElementById('cancelRoadDamageBtn').addEventListener('click', closeRoadDamageModal);
|
||||
|
||||
Reference in New Issue
Block a user