eSPJ/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml

804 lines
34 KiB
Plaintext

@{
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
ViewData["Title"] = "Home Page";
}
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen pb-28 relative font-sans">
<div class="bg-upst text-white px-6 pt-10 pb-24 rounded-b-[50px] shadow-2xl relative overflow-hidden">
<img src="@Url.Content("~/driver/upst_white.svg")" alt="UPST Logo" class="absolute top-4 left-6 w-20 h-auto opacity-20">
<div class="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -mr-16 -mt-16"></div>
<div class="flex justify-between items-start relative z-10">
<div class="space-y-1">
<p class="text-[10px] pt-5 font-black uppercase tracking-[0.2em] text-orange-200 opacity-80">Akun Driver</p>
<h1 class="text-2xl font-extrabold tracking-tight">Bonny Agung</h1>
<div class="flex items-center gap-2 mt-2 bg-black/20 w-fit px-3 py-1 rounded-full border border-white/10">
<span class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></span>
<span class="text-[10px] font-bold uppercase" id="currentDateTime">Loading...</span>
</div>
</div>
<button id="profileMenuButton" class="w-12 h-12 rounded-2xl bg-white/10 border border-white/20 backdrop-blur-lg flex items-center justify-center shadow-lg transition-transform active:scale-90">
<i class="w-6 h-6 text-white" data-lucide="user"></i>
</button>
</div>
<div id="profileMenuDropdown" class="absolute top-24 right-6 w-36 bg-white rounded-2xl shadow-2xl py-2 z-50 hidden border border-gray-100">
<form method="post" asp-controller="Auth" asp-action="Logout">
<button type="submit" class="flex items-center gap-3 w-full px-4 py-3 text-xs font-bold text-red-600 hover:bg-red-50 transition-colors">
<i class="w-4 h-4" data-lucide="log-out"></i> Logout
</button>
</form>
</div>
</div>
<div class="px-4 -mt-16 relative z-20">
<div class="bg-upst-light rounded-3xl p-4 border border-gray-100 mb-6">
<div class="flex items-center gap-4 group cursor-pointer" id="userLocationBtn">
<div class="w-10 h-10 bg-upst rounded-2xl flex items-center justify-center flex-shrink-0 group-active:scale-90 transition-transform">
<i class="w-5 h-5 text-white" data-lucide="map-pin"></i>
</div>
<div class="flex-1 min-w-0">
<p class="text-[9px] font-black text-gray-400 uppercase tracking-wider leading-none mb-1">Lokasi Saat Ini</p>
<p id="userLocation" class="text-xs line-clamp-3 font-bold text-gray-700 italic">Mendeteksi lokasi...</p>
</div>
<i class="w-4 h-4 text-gray-700" data-lucide="refresh-cw"></i>
</div>
</div>
<div class="grid grid-cols-2 gap-2 mb-4 p-1 bg-white rounded-2xl border border-gray-100 shadow-sm">
<button id="tabSpjButton" type="button" class="px-3 py-2 text-xs font-black rounded-xl bg-upst text-white transition-all">
SPJ
</button>
<button id="tabMapsButton" type="button" class="px-3 py-2 text-xs font-black rounded-xl text-gray-500 bg-gray-100 transition-all">
MAPS
</button>
</div>
<div id="spjCardPanel" class="bg-upst rounded-[35px] overflow-hidden shadow-2xl relative h-[300px]">
<div class="bg-white/10 backdrop-blur-md p-5 flex justify-between items-center border-b border-white/10">
<div>
<p class="text-[10px] font-black text-green-100 uppercase tracking-widest">Nomor SPJ</p>
<p class="text-white font-mono font-bold text-sm">SPJ/07-2025/PKM/000476</p>
</div>
<div class="text-right">
<span class="bg-white text-green-600 text-[10px] font-black px-3 py-1 rounded-lg shadow-sm">B 9632 TOR</span>
</div>
</div>
<div class="p-6 relative h-[236px]">
<div class="flex justify-between items-center h-full">
<div class="space-y-4">
<div>
<p class="text-[10px] text-green-100 font-bold uppercase opacity-80">Tujuan Pembuangan</p>
<h2 class="text-2xl font-black text-white tracking-tight leading-tight">JRC Rorotan</h2>
<p class="text-xs text-green-100/70 font-medium">(JRC 005)</p>
</div>
</div>
<button id="qrCodeTrigger" class="w-16 h-16 bg-white rounded-[24px] flex items-center justify-center shadow-2xl border-4 border-green-600/20 active:scale-90 transition-transform">
<img src="@Url.Content("~/driver/images/qr.png")" alt="QR" class="w-10 h-10 object-contain">
</button>
</div>
<img src="@Url.Content("~/driver/tree.svg")" class="absolute -left-4 -bottom-4 w-20 h-20 opacity-10 pointer-events-none" alt="">
</div>
</div>
<div id="mapsCardPanel" class="bg-upst rounded-[35px] overflow-hidden shadow-2xl relative h-[300px] flex-col hidden">
<div class="bg-white/10 backdrop-blur-md p-5 flex justify-between items-center border-b border-white/10">
<div>
<p class="text-[10px] font-black text-green-100 uppercase tracking-widest">Maps</p>
<p class="text-white font-mono font-bold text-sm">Rute Pengangkutan</p>
</div>
<div class="text-right">
<span class="bg-white text-green-600 text-[10px] font-black px-3 py-1 rounded-lg shadow-sm">LIVE</span>
</div>
</div>
<div class="p-6 relative h-[236px]">
<div id="pickupMap" class="h-full w-full z-10 rounded-3xl overflow-hidden border border-white/20"></div>
<button id="recenterMapButton" type="button" class="absolute top-9 right-9 z-20 w-11 h-11 bg-white/95 text-upst rounded-2xl shadow-xl border border-white/70 backdrop-blur flex items-center justify-center active:scale-95 transition-transform" title="Tampilkan semua lokasi">
<i class="w-5 h-5" data-lucide="locate-fixed"></i>
</button>
<img src="@Url.Content("~/driver/tree.svg")" class="absolute -left-4 -bottom-4 w-20 h-20 opacity-10 pointer-events-none" alt="">
</div>
</div>
</div>
<div class="px-6 mt-10">
<div class="flex justify-between items-end mb-6">
<div>
<h3 class="text-xl font-black text-gray-800 tracking-tighter">Lokasi</h3>
<p class="text-[10px] text-gray-400 font-bold uppercase">Pengangkutan</p>
</div>
<button id="addPickupButton" class="bg-upst text-white p-3 rounded-2xl shadow-lg shadow-gray-200 hover:brightness-110 active:scale-90 transition-all">
<i class="w-5 h-5" data-lucide="plus"></i>
</button>
</div>
<div class="space-y-6 relative">
<div class="absolute left-6 top-2 bottom-2 w-0.5 bg-gray-200 rounded-full"></div>
<a href="@Url.Action("Index", "DetailPenjemputan")" class="block relative pl-12 group">
<div class="absolute left-4 top-1 w-4 h-4 bg-white border-4 border-gray-400 rounded-full z-10"></div>
<div class="bg-white p-4 rounded-3xl border border-gray-100 shadow-sm group-active:bg-gray-50 transition-colors">
<div class="flex justify-between items-start mb-2">
<span class="text-[9px] font-black text-gray-400 uppercase tracking-widest">Proses</span>
<i class="w-4 h-4 text-gray-300" data-lucide="loader"></i>
</div>
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Mitra Utama - Shell Radio Dalam</h4>
<div class="flex items-center gap-1 mt-2 text-gray-500">
<i class="w-3 h-3" data-lucide="map"></i>
<p class="text-[10px] truncate italic">Jakarta Timur 13470</p>
</div>
<div class="mt-2">
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">Ada 3 TPS</span>
</div>
</div>
</a>
<a href="@Url.Action("DetailSelesai", "DetailPenjemputan")" class="block relative pl-12 group">
<div class="absolute left-4 top-1 w-4 h-4 bg-green-500 rounded-full z-10 ring-4 ring-green-100"></div>
<div class="bg-green-50/50 p-4 rounded-3xl border border-green-100 shadow-sm group-active:bg-green-100 transition-colors">
<div class="flex justify-between items-start mb-2">
<span class="text-[9px] font-black text-green-600 uppercase italic tracking-tighter">Selesai</span>
<i class="w-4 h-4 text-green-500" data-lucide="check-circle-2"></i>
</div>
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Mitra Utama - Shell Radio Dalam</h4>
<p class="text-[10px] text-green-700/60 mt-2">Jakarta Selatan</p>
<div class="mt-2">
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">Ada 3 TPS</span>
</div>
</div>
</a>
<a href="@Url.Action("DetailSelesaiTanpaTps", "DetailPenjemputan")" class="block relative pl-12 group">
<div class="absolute left-4 top-1 w-4 h-4 bg-green-500 rounded-full z-10 ring-4 ring-green-100"></div>
<div class="bg-green-50/50 p-4 rounded-3xl border border-green-100 shadow-sm group-active:bg-green-100 transition-colors">
<div class="flex justify-between items-start mb-2">
<span class="text-[9px] font-black text-green-600 uppercase italic tracking-tighter">Selesai</span>
<i class="w-4 h-4 text-green-500" data-lucide="check-circle-2"></i>
</div>
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
<p class="text-[10px] text-green-700/60 mt-2">Duren Sawit, Jakarta Timur</p>
<div class="mt-2">
<span class="text-[9px] font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded-lg">Tidak ada TPS</span>
</div>
</div>
</a>
<a href="@Url.Action("DetailBatal", "DetailPenjemputan")" class="block relative pl-12 group">
<div class="absolute left-4 top-1 w-4 h-4 bg-red-500 rounded-full z-10 ring-4 ring-red-100"></div>
<div class="bg-red-50/50 p-4 rounded-3xl border border-red-100 shadow-sm group-active:bg-red-100 transition-colors">
<div class="flex justify-between items-start mb-2">
<span class="text-[9px] font-black text-red-600 uppercase italic tracking-tighter">Batal</span>
<i class="w-4 h-4 text-red-500" data-lucide="x-circle"></i>
</div>
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
<p class="text-[10px] text-red-700/60 mt-2">Klender, Jakarta Timur</p>
</div>
</a>
</div>
</div>
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
</div>
<div id="qrCodeModal" class="fixed inset-0 bg-upst/90 backdrop-blur-xl items-center justify-center z-[100] hidden p-8 transition-all">
<div class="w-full max-w-xs space-y-6">
<div class="bg-white rounded-[45px] p-8 shadow-2xl relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-2 bg-upst-light"></div>
<div class="text-center mb-6">
<p class="text-[10px] font-black text-gray-400 uppercase tracking-[0.3em] mb-1">Scanner Siap</p>
<h3 class="text-lg font-black text-gray-800">Verifikasi SPJ</h3>
</div>
<div class="bg-gray-50 p-6 rounded-[35px] border-2 border-gray-100 shadow-inner">
<img src="@Url.Content("~/driver/images/qr.png")" alt="QR Code" class="w-full h-auto">
</div>
<div class="mt-6 text-center">
<p class="text-[11px] font-mono text-gray-500 break-all bg-gray-100 py-2 px-3 rounded-xl">SPJ/07-2025/PKM/000476</p>
</div>
</div>
<button id="closeQrModal" class="w-full py-4 bg-white/10 border border-white/20 rounded-2xl text-white font-bold text-sm backdrop-blur-md active:scale-95 transition-all">
TUTUP
</button>
</div>
</div>
<div id="addPickupModal" class="fixed inset-0 bg-upst/90 backdrop-blur-xl items-center justify-center z-[100] hidden p-8 transition-all">
<div class="w-full max-w-xs space-y-6">
<div class="bg-white rounded-[45px] p-8 shadow-2xl relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-2 bg-upst-light"></div>
<div class="text-center mb-6">
<p class="text-[10px] font-black text-gray-400 uppercase tracking-[0.3em] mb-1">Tambah Lokasi</p>
<h3 class="text-lg font-black text-gray-800">Pengangkutan</h3>
</div>
<div class="space-y-4">
<div>
<label class="text-xs font-bold text-gray-600 uppercase tracking-wider">Pilih Lokasi Pengangkutan</label>
<select id="pickupSelect" class="w-full mt-2"></select>
</div>
</div>
<div class="mt-6 text-center">
<button id="closeAddModal" class="w-full py-4 bg-upst text-white rounded-2xl font-bold text-sm active:scale-95 transition-all">
TUTUP
</button>
</div>
</div>
</div>
</div>
<register-block dynamic-section="scripts" key="jsHomeIndex">
<script>
document.addEventListener("DOMContentLoaded", function () {
const userLocationEl = document.getElementById("userLocation");
function reverseGeocode(lat, lng) {
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
.then(res => res.json())
.then(data => {
const address = data.display_name || `${lat}, ${lng}`;
userLocationEl.textContent = address;
localStorage.setItem("user_latitude", lat);
localStorage.setItem("user_longitude", lng);
localStorage.setItem("user_address", address);
})
.catch(() => {
userLocationEl.textContent = `${lat}, ${lng}`;
});
}
function getLocationUpdate() {
if ("geolocation" in navigator) {
userLocationEl.textContent = "Mendeteksi lokasi baru...";
navigator.geolocation.getCurrentPosition(
function (position) {
const lat = position.coords.latitude.toFixed(6);
const lng = position.coords.longitude.toFixed(6);
reverseGeocode(lat, lng);
},
function () {
userLocationEl.textContent = "Lokasi tidak diizinkan";
}
);
} else {
userLocationEl.textContent = "Browser tidak mendukung lokasi";
}
}
const savedAddress = localStorage.getItem("user_address");
if (savedAddress) {
userLocationEl.textContent = savedAddress;
} else {
getLocationUpdate();
}
// Update Lokasi cuy
userLocationEl.addEventListener("click", function () {
getLocationUpdate();
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const tabSpjButton = document.getElementById("tabSpjButton");
const tabMapsButton = document.getElementById("tabMapsButton");
const spjCardPanel = document.getElementById("spjCardPanel");
const mapsCardPanel = document.getElementById("mapsCardPanel");
if (!tabSpjButton || !tabMapsButton || !spjCardPanel || !mapsCardPanel) return;
function activateTab(tab) {
const isSpj = tab === "spj";
tabSpjButton.classList.toggle("bg-upst", isSpj);
tabSpjButton.classList.toggle("text-white", isSpj);
tabSpjButton.classList.toggle("bg-gray-100", !isSpj);
tabSpjButton.classList.toggle("text-gray-500", !isSpj);
tabMapsButton.classList.toggle("bg-upst", !isSpj);
tabMapsButton.classList.toggle("text-white", !isSpj);
tabMapsButton.classList.toggle("bg-gray-100", isSpj);
tabMapsButton.classList.toggle("text-gray-500", isSpj);
spjCardPanel.classList.toggle("hidden", !isSpj);
mapsCardPanel.classList.toggle("hidden", isSpj);
mapsCardPanel.classList.toggle("flex", !isSpj);
if (!isSpj) {
document.dispatchEvent(new CustomEvent("spj:show-maps"));
}
}
tabSpjButton.addEventListener("click", function () {
activateTab("spj");
});
tabMapsButton.addEventListener("click", function () {
activateTab("maps");
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const mapEl = document.getElementById("pickupMap");
const mapsCardPanel = document.getElementById("mapsCardPanel");
const recenterMapButton = document.getElementById("recenterMapButton");
if (!mapEl) return;
let mapInstance = null;
let mapBounds = null;
let mapInitialized = false;
function fitAllLocations() {
if (!mapInstance || !mapBounds) return;
mapInstance.invalidateSize();
if (Array.isArray(mapBounds) && mapBounds.length === 1) {
mapInstance.setView(mapBounds[0], 16);
return;
}
mapInstance.fitBounds(mapBounds, {
padding: [24, 24],
maxZoom: 17
});
}
function ensureLeaflet() {
return new Promise((resolve, reject) => {
if (window.L && typeof window.L.map === "function") {
resolve();
return;
}
if (!document.getElementById("leaflet-css")) {
const css = document.createElement("link");
css.id = "leaflet-css";
css.rel = "stylesheet";
css.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
document.head.appendChild(css);
}
const existingScript = document.getElementById("leaflet-js");
if (existingScript) {
existingScript.addEventListener("load", resolve, { once: true });
existingScript.addEventListener("error", reject, { once: true });
return;
}
const script = document.createElement("script");
script.id = "leaflet-js";
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}
function toNumber(value) {
const num = Number(value);
return Number.isFinite(num) ? num : null;
}
function isValidLatLng(lat, lng) {
return lat !== null && lng !== null && Math.abs(lat) <= 90 && Math.abs(lng) <= 180;
}
function normalizeLatLng(item) {
const lat = toNumber(item.latitude);
const lng = toNumber(item.longitude);
if (isValidLatLng(lat, lng)) return { lat, lng };
if (isValidLatLng(lng, lat)) return { lat: lng, lng: lat };
return null;
}
async function geocodeFromAddress(address) {
if (!address) return null;
try {
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1`);
const data = await res.json();
if (!Array.isArray(data) || data.length === 0) return null;
const lat = toNumber(data[0].lat);
const lng = toNumber(data[0].lon);
return isValidLatLng(lat, lng) ? { lat, lng } : null;
} catch {
return null;
}
}
async function getCoordinates(item) {
const normalized = normalizeLatLng(item);
if (normalized) return normalized;
return geocodeFromAddress(item.alamat);
}
async function getRoadRouteLatLngs(latLngs) {
if (!Array.isArray(latLngs) || latLngs.length < 2) return null;
try {
const coordString = latLngs
.map(([lat, lng]) => `${lng},${lat}`)
.join(";");
const url = `https://router.project-osrm.org/route/v1/driving/${coordString}?overview=full&geometries=geojson&steps=false&alternatives=false`;
const res = await fetch(url);
if (!res.ok) return null;
const data = await res.json();
const coords = data?.routes?.[0]?.geometry?.coordinates;
if (!Array.isArray(coords) || coords.length < 2) return null;
return coords
.map(pair => [toNumber(pair?.[1]), toNumber(pair?.[0])])
.filter(([lat, lng]) => isValidLatLng(lat, lng));
} catch {
return null;
}
}
async function initMap() {
try {
if (mapInitialized && mapInstance) {
setTimeout(() => {
fitAllLocations();
}, 80);
return;
}
await ensureLeaflet();
const res = await fetch("@Url.Content("~/driver/json/pengangkutan.json")");
const payload = await res.json();
const rows = Array.isArray(payload?.data) ? payload.data : [];
const points = [];
for (const row of rows) {
const coords = await getCoordinates(row);
if (coords) points.push({ ...row, ...coords });
}
if (!points.length) {
mapEl.innerHTML = '<div class="h-full flex items-center justify-center text-xs font-semibold text-gray-500">Titik pengangkutan tidak ditemukan</div>';
return;
}
mapInstance = L.map("pickupMap", {
zoomControl: true,
attributionControl: true
});
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; OpenStreetMap"
}).addTo(mapInstance);
const pickupIcon = L.icon({
iconUrl: "@Url.Content("~/driver/images/loc2.svg")",
iconSize: [40, 40],
iconAnchor: [15, 30],
popupAnchor: [0, -28]
});
const latLngs = points.map(p => [p.lat, p.lng]);
points.forEach((point, index) => {
L.marker([point.lat, point.lng], { icon: pickupIcon })
.addTo(mapInstance)
.bindPopup(`<b>${index + 1}. ${point.name || "Lokasi"}</b><br>${point.alamat || "-"}`);
});
let routeLatLngs = latLngs;
if (latLngs.length > 1) {
const routed = await getRoadRouteLatLngs(latLngs);
if (routed && routed.length > 1) {
routeLatLngs = routed;
}
L.polyline(routeLatLngs, {
color: "#0f2a3f",
weight: 4,
opacity: 0.9
}).addTo(mapInstance);
}
mapBounds = routeLatLngs;
fitAllLocations();
mapInitialized = true;
setTimeout(() => {
fitAllLocations();
}, 80);
} catch (err) {
mapEl.innerHTML = '<div class="h-full flex items-center justify-center text-xs font-semibold text-red-500">Gagal memuat peta</div>';
console.error("Map init error:", err);
}
}
document.addEventListener("spj:show-maps", function () {
initMap();
});
if (recenterMapButton) {
recenterMapButton.addEventListener("click", async function () {
if (!mapInitialized || !mapInstance) {
await initMap();
return;
}
fitAllLocations();
});
}
if (mapsCardPanel && !mapsCardPanel.classList.contains("hidden")) {
initMap();
}
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const btn = document.getElementById("profileMenuButton");
const dropdown = document.getElementById("profileMenuDropdown");
btn.addEventListener("click", function (e) {
e.stopPropagation();
dropdown.classList.toggle("hidden");
});
document.addEventListener("click", function () {
dropdown.classList.add("hidden");
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const qrTrigger = document.getElementById("qrCodeTrigger");
const qrModal = document.getElementById("qrCodeModal");
const closeModal = document.getElementById("closeQrModal");
let originalBrightness = null;
let wakeLock = null;
async function setMaxBrightness() {
try {
if ('wakeLock' in navigator) {
wakeLock = await navigator.wakeLock.request('screen');
}
if ('screen' in navigator && 'brightness' in navigator.screen) {
originalBrightness = navigator.screen.brightness;
navigator.screen.brightness = 1.0;
}
} catch (err) {
console.log('Brightness control not supported:', err);
}
}
async function restoreOriginalBrightness() {
try {
if (wakeLock) {
await wakeLock.release();
wakeLock = null;
}
if ('screen' in navigator && 'brightness' in navigator.screen && originalBrightness !== null) {
navigator.screen.brightness = originalBrightness;
}
} catch (err) {
console.log('Error restoring brightness:', err);
}
}
qrTrigger.addEventListener("click", function () {
qrModal.classList.remove("hidden");
qrModal.classList.add("flex");
setMaxBrightness();
if ('vibrate' in navigator) {
navigator.vibrate(50);
}
});
function closeQrCodeModal() {
qrModal.classList.add("hidden");
qrModal.classList.remove("flex");
restoreOriginalBrightness();
}
closeModal.addEventListener("click", closeQrCodeModal);
qrModal.addEventListener("click", function (e) {
if (e.target === qrModal) {
closeQrCodeModal();
}
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !qrModal.classList.contains("hidden")) {
closeQrCodeModal();
}
});
document.addEventListener("visibilitychange", function () {
if (document.hidden && !qrModal.classList.contains("hidden")) {
restoreOriginalBrightness();
} else if (!document.hidden && !qrModal.classList.contains("hidden")) {
setMaxBrightness();
}
});
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const addModal = document.getElementById("addPickupModal");
const closeAddModal = document.getElementById("closeAddModal");
const pickupSelect = document.getElementById("pickupSelect");
const plusButton = document.getElementById("addPickupButton");
function ensureSelect2() {
return new Promise((resolve, reject) => {
if (window.jQuery && window.jQuery.fn.select2) {
resolve();
return;
}
if (!document.getElementById("select2-css")) {
const css = document.createElement("link");
css.id = "select2-css";
css.rel = "stylesheet";
css.href = "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css";
document.head.appendChild(css);
}
const existingScript = document.getElementById("select2-js");
if (existingScript) {
existingScript.addEventListener("load", resolve, { once: true });
existingScript.addEventListener("error", reject, { once: true });
return;
}
const script = document.createElement("script");
script.id = "select2-js";
script.src = "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js";
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3;
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return Math.round(R * c);
}
function getCurrentLocation() {
const lat = parseFloat(localStorage.getItem("user_latitude"));
const lng = parseFloat(localStorage.getItem("user_longitude"));
return { lat, lng };
}
async function initAddModal() {
try {
await ensureSelect2();
const res = await fetch("@Url.Content("~/driver/json/pickup_locations.json")");
const payload = await res.json();
const locations = Array.isArray(payload?.data) ? payload.data : [];
const current = getCurrentLocation();
const data = locations.map((loc, index) => {
let distanceText = '';
if (current.lat && current.lng && loc.latitude && loc.longitude) {
const distance = calculateDistance(current.lat, current.lng, loc.latitude, loc.longitude);
distanceText = ` (${distance} m)`;
}
return {
id: index,
text: `${loc.name} - ${loc.alamat}${distanceText}`,
lat: loc.latitude,
lng: loc.longitude,
name: loc.name,
alamat: loc.alamat,
distance: distanceText
};
});
$(pickupSelect).select2({
data: data,
placeholder: "Cari lokasi pengangkutan...",
allowClear: true,
width: '100%',
templateResult: function (item) {
if (!item.id) return item.text;
return $(`<div><strong>${item.name}</strong><br><small>${item.alamat}${item.distance}</small></div>`);
},
templateSelection: function (item) {
if (!item.id) return item.text;
return `${item.name} - ${item.alamat}${item.distance}`;
}
});
$(pickupSelect).on('select2:select', function (e) {
});
$(pickupSelect).on('select2:clear', function () {
});
} catch (err) {
console.error("Error initializing add modal:", err);
}
}
plusButton.addEventListener("click", function () {
addModal.classList.remove("hidden");
addModal.classList.add("flex");
initAddModal();
});
function closeAddPickupModal() {
addModal.classList.add("hidden");
addModal.classList.remove("flex");
$(pickupSelect).val(null).trigger('change');
}
closeAddModal.addEventListener("click", closeAddPickupModal);
addModal.addEventListener("click", function (e) {
if (e.target === addModal) {
closeAddPickupModal();
}
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !addModal.classList.contains("hidden")) {
closeAddPickupModal();
}
});
});
</script>
<script>
function updateDateTime() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
const formatted = now.toLocaleDateString('id-ID', options);
document.getElementById('currentDateTime').textContent = formatted;
}
updateDateTime();
setInterval(updateDateTime, 1000);
</script>
</register-block>