diff --git a/resources/views/menu_v1/reports/vehicle_trips.blade.php b/resources/views/menu_v1/reports/vehicle_trips.blade.php
index ef7e2cc..a32b5aa 100644
--- a/resources/views/menu_v1/reports/vehicle_trips.blade.php
+++ b/resources/views/menu_v1/reports/vehicle_trips.blade.php
@@ -25,8 +25,8 @@
@if ($user_role == \App\Models\Users::ROLE_VENDOR || $user_role == \App\Models\Users::ROLE_ADMIN)
@can('vehicle.create')
+
+ -->
@endcan
{{--
@@ -300,6 +300,81 @@
return `Site ${site.address}, Timor-Leste`;
}
+ // ================================
+ // REVERSE GEOCODING (Nominatim) - fallback ketika startLoc/finishLoc kosong
+ // ================================
+
+ // Cache in-memory supaya koordinat yang sama tidak fetch berulang
+ const geocodeCache = new Map();
+
+ // Antrian sederhana supaya request ke Nominatim tidak lebih dari 1/detik
+ let geocodeQueue = Promise.resolve();
+
+ function reverseGeocode(lat, lng) {
+ if (lat === null || lng === null || lat === undefined || lng === undefined || isNaN(lat) || isNaN(lng)) {
+ return Promise.resolve(null);
+ }
+
+ const key = `${lat.toFixed(5)},${lng.toFixed(5)}`;
+ if (geocodeCache.has(key)) {
+ return Promise.resolve(geocodeCache.get(key));
+ }
+
+ // Chain ke queue supaya jaraknya minimal ~1100ms antar request
+ geocodeQueue = geocodeQueue.then(() =>
+ new Promise(resolve => setTimeout(resolve, 1100))
+ );
+
+ return geocodeQueue.then(async () => {
+ try {
+ const url =
+ `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`;
+ const res = await fetch(url, {
+ headers: {
+ 'Accept-Language': 'id'
+ }
+ });
+ const json = await res.json();
+ const address = json && json.display_name ? json.display_name : null;
+ geocodeCache.set(key, address);
+ return address;
+ } catch (e) {
+ console.error('Reverse geocode gagal:', e);
+ geocodeCache.set(key, null);
+ return null;
+ }
+ });
+ }
+
+ // Isi row.startLoc / row.finishLoc yang masih kosong (null/undefined/'')
+ // menggunakan hasil reverse geocode dari koordinat start_lat_long / finish_lat_long.
+ // Nilai disimpan dalam bentuk encodeURIComponent supaya konsisten dengan
+ // field startLoc/finishLoc bawaan API yang memang sudah URL-encoded,
+ // karena proses render memanggil decodeURIComponent(row.startLoc).
+ async function fillMissingLocations(rows) {
+ if (!Array.isArray(rows)) return;
+
+ for (const row of rows) {
+ if (!row.startLoc) {
+ const {
+ lat,
+ lng
+ } = parseLatLong(row.start_lat_long);
+ const address = await reverseGeocode(lat, lng);
+ row.startLoc = address ? encodeURIComponent(address) : null;
+ }
+
+ if (!row.finishLoc) {
+ const {
+ lat,
+ lng
+ } = parseLatLong(row.finish_lat_long);
+ const address = await reverseGeocode(lat, lng);
+ row.finishLoc = address ? encodeURIComponent(address) : null;
+ }
+ }
+ }
+
// ================================
// END SITE LABELING
// ================================
@@ -346,7 +421,16 @@
&poolcode=${$('#poolcode').val() || ''}
`,
type: 'GET',
- success: function(json) {
+ success: async function(json) {
+ // Isi startLoc/finishLoc yang kosong via Nominatim
+ // SEBELUM tabel dirender, karena render() DataTables bersifat sinkron
+ // dan tidak bisa menunggu fetch() di dalamnya.
+ try {
+ await fillMissingLocations(json && json.data);
+ } catch (e) {
+ console.error('Gagal melengkapi lokasi via reverse geocode:', e);
+ }
+
callback(json);
},
error: function(xhr, status, error) {
@@ -417,76 +501,43 @@
// ==== END KOLOM BARU ====
{
data: 'start',
- render: (data, type, row, meta) => {
- // Parse start_lat_long -> { lat, lng }
+ render: (data, type, row) => {
const {
lat: startLat,
lng: startLng
} = parseLatLong(row.start_lat_long);
-
- // saat export, hanya tampilkan lokasi tanpa tanggal/jam
- if (type === 'export') {
- const nearestSite = findNearestSite(startLat, startLng);
-
- if (nearestSite) {
- return formatSiteLabel(nearestSite);
- }
- return Helper.shortenText(decodeURIComponent(row.startLoc ||
- 'address'), 255) || "-";
- }
-
const nearestSite = findNearestSite(startLat, startLng);
- if (nearestSite) {
- return `${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}
`+formatSiteLabel(nearestSite);
- }
- return `
- ${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}
- ${Helper.shortenText(decodeURIComponent(row.startLoc || 'address'), 255) || "-"}
- `;
+ // Prioritas: site terdaftar > startLoc (dari API atau hasil reverse geocode) > '-'
+ const locationLabel = nearestSite ?
+ formatSiteLabel(nearestSite) :
+ (row.startLoc ?
+ Helper.shortenText(decodeURIComponent(row.startLoc), 255) :
+ '-');
+
+ if (type === 'export') return locationLabel;
+
+ return `${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}
${locationLabel}`;
}
},
{
data: 'finish',
render: (data, type, row) => {
- // Parse finish_lat_long -> { lat, lng }
const {
lat: finishLat,
lng: finishLng
} = parseLatLong(row.finish_lat_long);
-
- // Saat export Excel
- if (type === 'export') {
- const nearestSite = findNearestSite(finishLat, finishLng);
-
- if (nearestSite) {
- return formatSiteLabel(nearestSite);
- }
-
- return Helper.shortenText(
- decodeURIComponent(row.finishLoc || 'address'),
- 255
- ) || '-';
- }
-
const nearestSite = findNearestSite(finishLat, finishLng);
- if (nearestSite) {
- return `${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}
`+formatSiteLabel(nearestSite);
- }
+ const locationLabel = nearestSite ?
+ formatSiteLabel(nearestSite) :
+ (row.finishLoc ?
+ Helper.shortenText(decodeURIComponent(row.finishLoc), 255) :
+ '-');
- // Tampilan di tabel
- return `
- ${moment.unix(data + AppState.TIMEFIX)
- .format('DD MMM YYYY HH:mm:ss')}
-
- ${
- Helper.shortenText(
- decodeURIComponent(row.finishLoc || 'address'),
- 255
- ) || '-'
- }
-`;
+ if (type === 'export') return locationLabel;
+
+ return `${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}
${locationLabel}`;
}
},
{