This commit is contained in:
wayanrivan
2026-08-01 21:22:55 +07:00
parent 994608327a
commit ff47f3f932

View File

@ -11,6 +11,12 @@
@section('customcss') @section('customcss')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" /> integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<style>
.geo-pending {
color: #999;
font-style: italic;
}
</style>
@endsection @endsection
@section('content') @section('content')
@ -346,33 +352,59 @@
}); });
} }
// Isi row.startLoc / row.finishLoc yang masih kosong (null/undefined/'') // Bikin id unik untuk elemen placeholder lokasi per baris+kolom,
// menggunakan hasil reverse geocode dari koordinat start_lat_long / finish_lat_long. // dipakai untuk update teks langsung via DOM setelah geocode selesai di background.
// Nilai disimpan dalam bentuk encodeURIComponent supaya konsisten dengan function geoElementId(row, kind) {
// field startLoc/finishLoc bawaan API yang memang sudah URL-encoded, // kind: 'start' | 'finish'
// karena proses render memanggil decodeURIComponent(row.startLoc). return `geo-${kind}-${row.id}-${row.trip_id}`;
async function fillMissingLocations(rows) { }
// Jalankan reverse geocode di BACKGROUND (tidak menunggu / tidak blocking render tabel).
// Tabel sudah tampil duluan dengan placeholder "Load for location...", lalu begitu
// Nominatim balas untuk satu titik, teks placeholder-nya langsung di-update di tempat
// (tanpa redraw seluruh tabel), row demi row sesuai antrian reverseGeocode.
function scheduleBackgroundGeocode(rows) {
if (!Array.isArray(rows)) return; if (!Array.isArray(rows)) return;
for (const row of rows) { rows.forEach(row => {
if (!row.startLoc) { const {
const { lat: startLat,
lat, lng: startLng
lng } = parseLatLong(row.start_lat_long);
} = parseLatLong(row.start_lat_long); const startNearestSite = findNearestSite(startLat, startLng);
const address = await reverseGeocode(lat, lng);
row.startLoc = address ? encodeURIComponent(address) : null; // hanya perlu geocode kalau bukan site terdaftar & startLoc memang kosong
if (!startNearestSite && !row.startLoc) {
geocodeAndUpdateCell(geoElementId(row, 'start'), startLat, startLng);
} }
if (!row.finishLoc) { const {
const { lat: finishLat,
lat, lng: finishLng
lng } = parseLatLong(row.finish_lat_long);
} = parseLatLong(row.finish_lat_long); const finishNearestSite = findNearestSite(finishLat, finishLng);
const address = await reverseGeocode(lat, lng);
row.finishLoc = address ? encodeURIComponent(address) : null; if (!finishNearestSite && !row.finishLoc) {
geocodeAndUpdateCell(geoElementId(row, 'finish'), finishLat, finishLng);
} }
} });
}
// Panggil reverseGeocode (sudah antri otomatis max 1 request/detik),
// begitu hasilnya datang, cari elemen placeholder di DOM lewat id-nya
// dan replace teksnya. Kalau elemen sudah tidak ada (misal tabel sudah
// di-reload/filter berubah), update di-skip secara aman.
function geocodeAndUpdateCell(elId, lat, lng) {
reverseGeocode(lat, lng).then(address => {
const $el = $('#' + elId);
if (!$el.length) return; // tabel sudah berganti, abaikan hasil basi ini
if (address) {
$el.text(Helper.shortenText(address, 255)).removeClass('geo-pending');
} else {
$el.text('-').removeClass('geo-pending');
}
});
} }
// ================================ // ================================
@ -421,17 +453,22 @@
&poolcode=${$('#poolcode').val() || ''} &poolcode=${$('#poolcode').val() || ''}
`, `,
type: 'GET', type: 'GET',
success: async function(json) { success: function(json) {
// Isi startLoc/finishLoc yang kosong via Nominatim // Tampilkan tabel DULUAN dengan semua data apa adanya
// SEBELUM tabel dirender, karena render() DataTables bersifat sinkron // (baris yang startLoc/finishLoc kosong akan muncul
// dan tidak bisa menunggu fetch() di dalamnya. // sebagai placeholder "Load for location...").
try {
await fillMissingLocations(json && json.data);
} catch (e) {
console.error('Gagal melengkapi lokasi via reverse geocode:', e);
}
callback(json); callback(json);
// Reset antrian geocode supaya request dari load sebelumnya
// (misal filter lama) tidak numpuk / membuang kuota Nominatim.
geocodeQueue = Promise.resolve();
// Baru SETELAH itu, isi lokasi yang kosong di background.
// setTimeout 0 supaya DOM tabel sudah pasti ter-render dulu
// sebelum kita cari elemen placeholder-nya.
setTimeout(() => {
scheduleBackgroundGeocode(json && json.data);
}, 0);
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
if (status !== 'abort') { if (status !== 'abort') {
@ -508,12 +545,23 @@
} = parseLatLong(row.start_lat_long); } = parseLatLong(row.start_lat_long);
const nearestSite = findNearestSite(startLat, startLng); const nearestSite = findNearestSite(startLat, startLng);
// Prioritas: site terdaftar > startLoc (dari API atau hasil reverse geocode) > '-' let locationLabel;
const locationLabel = nearestSite ? if (nearestSite) {
formatSiteLabel(nearestSite) : // Prioritas 1: site terdaftar
(row.startLoc ? locationLabel = formatSiteLabel(nearestSite);
Helper.shortenText(decodeURIComponent(row.startLoc), 255) : } else if (row.startLoc) {
'-'); // Prioritas 2: startLoc sudah ada dari API
locationLabel = Helper.shortenText(decodeURIComponent(row.startLoc), 255);
} else if (type === 'export') {
// Export bersifat sinkron, tidak bisa menunggu hasil geocode background
locationLabel = '-';
} else {
// Prioritas 3: kosong -> tampilkan placeholder dulu,
// nanti di-update otomatis oleh scheduleBackgroundGeocode()
// begitu hasil dari Nominatim datang.
locationLabel =
`<span id="${geoElementId(row, 'start')}" class="geo-pending">Load for location...</span>`;
}
if (type === 'export') return locationLabel; if (type === 'export') return locationLabel;
@ -529,11 +577,17 @@
} = parseLatLong(row.finish_lat_long); } = parseLatLong(row.finish_lat_long);
const nearestSite = findNearestSite(finishLat, finishLng); const nearestSite = findNearestSite(finishLat, finishLng);
const locationLabel = nearestSite ? let locationLabel;
formatSiteLabel(nearestSite) : if (nearestSite) {
(row.finishLoc ? locationLabel = formatSiteLabel(nearestSite);
Helper.shortenText(decodeURIComponent(row.finishLoc), 255) : } else if (row.finishLoc) {
'-'); locationLabel = Helper.shortenText(decodeURIComponent(row.finishLoc), 255);
} else if (type === 'export') {
locationLabel = '-';
} else {
locationLabel =
`<span id="${geoElementId(row, 'finish')}" class="geo-pending">Load for location...</span>`;
}
if (type === 'export') return locationLabel; if (type === 'export') return locationLabel;