From 4bdfba7ea318c1271aab50925da785b64af979ab Mon Sep 17 00:00:00 2001 From: jouel88 Date: Sun, 29 Mar 2026 20:08:13 +0700 Subject: [PATCH] google maps --- app/Http/Controllers/Api/PoinController.php | 21 +- .../Controllers/Api/PresensiController.php | 7 + app/Http/Requests/StoreKantorRequest.php | 1 + app/Http/Requests/UpdateKantorRequest.php | 1 + config/services.php | 4 + ..._03_29_123748_add_tipe_to_kantor_table.php | 25 ++ public/js/form-handler.js | 76 ++--- public/js/image-cropper.js | 175 +++++++++++ public/js/kantor-map.js | 283 ++++++++++-------- resources/views/kantor/index.blade.php | 223 +++++++++++--- resources/views/profile/edit.blade.php | 141 +++++++-- 11 files changed, 722 insertions(+), 235 deletions(-) create mode 100644 database/migrations/2026_03_29_123748_add_tipe_to_kantor_table.php create mode 100644 public/js/image-cropper.js diff --git a/app/Http/Controllers/Api/PoinController.php b/app/Http/Controllers/Api/PoinController.php index 7093e307..31acfc09 100644 --- a/app/Http/Controllers/Api/PoinController.php +++ b/app/Http/Controllers/Api/PoinController.php @@ -16,7 +16,7 @@ class PoinController extends Controller public function getExpiringPoints() { try { - $userId = auth()->user()->id; + $userId = auth()->id(); $poinService = new PoinService(); $poinExpiring = $poinService->getExpiringPoints($userId); @@ -45,7 +45,7 @@ public function getExpiringPoints() public function getPointHistory() { try { - $userId = auth()->user()->id; + $userId = auth()->id(); $poinService = new PoinService(); $history = $poinService->getPointHistory($userId); @@ -72,7 +72,7 @@ public function redeem(Request $request) } try { - $userId = auth()->user()->id; + $userId = auth()->id(); $jumlah = $request->jumlah; $keterangan = $request->keterangan; @@ -86,6 +86,21 @@ public function redeem(Request $request) preg_match('/\[(\d{4}-\d{2}-\d{2})\]/', $keterangan, $matches); $tanggalPenggunaan = isset($matches[1]) ? $matches[1] : now()->format('Y-m-d'); + $jadwal = \App\Models\JadwalKerja::with('shift') + ->where('id_user', $userId) + ->where('tanggal', $tanggalPenggunaan) + ->first(); + + if (!$jadwal || !$jadwal->shift) { + return ApiResponse::error('Jadwal kerja tidak ditemukan untuk tanggal ' . Carbon::parse($tanggalPenggunaan)->format('d/m/Y') . '.', 400); + } + + $waktuBatasPengajuan = Carbon::parse($tanggalPenggunaan . ' ' . $jadwal->shift->jam_mulai)->subHour(); + + if (now()->greaterThan($waktuBatasPengajuan)) { + return ApiResponse::error('Pengajuan poin harus dilakukan maksimal 1 jam sebelum jam kerja dimulai (Batas: ' . $waktuBatasPengajuan->format('d/m/Y H:i') . ').', 400); + } + PenggunaanPoin::create([ 'id_user' => $userId, 'tanggal_penggunaan' => $tanggalPenggunaan, diff --git a/app/Http/Controllers/Api/PresensiController.php b/app/Http/Controllers/Api/PresensiController.php index 5a7cfa4a..d0d244a9 100644 --- a/app/Http/Controllers/Api/PresensiController.php +++ b/app/Http/Controllers/Api/PresensiController.php @@ -169,6 +169,7 @@ public function store(Request $request) 'foto' => 'required|image|max:2048', 'status' => 'required|in:masuk,pulang', 'keterangan_luar_radius' => 'nullable|string|max:500', + 'alasan_telat' => 'nullable|string|max:500', ]); $user = Auth::user(); @@ -277,6 +278,12 @@ public function history(Request $request) 'verifikasi_wajah' => (bool) $item->verifikasi_wajah, 'status_validasi' => $item->id_validasi, 'alasan_penolakan' => $item->alasan_penolakan, + 'foto_masuk_url' => $item->foto_wajah_masuk + ? asset('storage/' . $item->foto_wajah_masuk) + : null, + 'foto_pulang_url' => $item->foto_wajah_pulang + ? asset('storage/' . $item->foto_wajah_pulang) + : null, ]; }); diff --git a/app/Http/Requests/StoreKantorRequest.php b/app/Http/Requests/StoreKantorRequest.php index 4a486acd..34d319a7 100644 --- a/app/Http/Requests/StoreKantorRequest.php +++ b/app/Http/Requests/StoreKantorRequest.php @@ -15,6 +15,7 @@ public function rules(): array { return [ 'nama_kantor' => ['required', 'string', 'max:255'], + 'tipe' => ['required', 'string', 'in:Pusat,Cabang'], 'alamat' => ['nullable', 'string'], 'latitude' => ['required', 'numeric', 'between:-90,90'], diff --git a/app/Http/Requests/UpdateKantorRequest.php b/app/Http/Requests/UpdateKantorRequest.php index f34f1b77..1af6de80 100644 --- a/app/Http/Requests/UpdateKantorRequest.php +++ b/app/Http/Requests/UpdateKantorRequest.php @@ -15,6 +15,7 @@ public function rules(): array { return [ 'nama_kantor' => ['required', 'string', 'max:255'], + 'tipe' => ['required', 'string', 'in:Pusat,Cabang'], 'alamat' => ['nullable', 'string'], 'latitude' => ['required', 'numeric', 'between:-90,90'], diff --git a/config/services.php b/config/services.php index 0f13a0f1..ee6b6887 100644 --- a/config/services.php +++ b/config/services.php @@ -23,4 +23,8 @@ ], ], + 'google_maps' => [ + 'key' => env('GOOGLE_MAPS_API_KEY'), + ], + ]; diff --git a/database/migrations/2026_03_29_123748_add_tipe_to_kantor_table.php b/database/migrations/2026_03_29_123748_add_tipe_to_kantor_table.php new file mode 100644 index 00000000..02dc4a0f --- /dev/null +++ b/database/migrations/2026_03_29_123748_add_tipe_to_kantor_table.php @@ -0,0 +1,25 @@ +string('tipe', 20)->default('Cabang')->after('nama_kantor'); + }); + } + + public function down(): void + { + Schema::table('kantor', function (Blueprint $table) { + $table->dropColumn('tipe'); + }); + } +}; diff --git a/public/js/form-handler.js b/public/js/form-handler.js index 39559d74..031ff949 100644 --- a/public/js/form-handler.js +++ b/public/js/form-handler.js @@ -1,45 +1,47 @@ -let isFormModified = false; +(function() { + let isFormModified = false; -document.addEventListener('DOMContentLoaded', function() { - - const form = document.querySelector('form'); - const submitBtn = document.getElementById('submitBtn'); - const submitText = document.getElementById('submitText'); - const submitSpinner = document.getElementById('submitSpinner'); + document.addEventListener('DOMContentLoaded', function() { + + const form = document.querySelector('form'); + const submitBtn = document.getElementById('submitBtn'); + const submitText = document.getElementById('submitText'); + const submitSpinner = document.getElementById('submitSpinner'); - if (form) { - form.addEventListener('change', () => { - isFormModified = true; - }); + if (form) { + form.addEventListener('change', () => { + isFormModified = true; + }); - form.addEventListener('submit', function() { - isFormModified = false; + form.addEventListener('submit', function() { + isFormModified = false; - if (submitBtn) { - submitBtn.disabled = true; - submitBtn.classList.add('opacity-75', 'cursor-not-allowed'); - } - if (submitSpinner) submitSpinner.classList.remove('hidden'); - if (submitText) submitText.textContent = 'Menyimpan...'; - }); - } - - window.addEventListener('beforeunload', function(e) { - if (isFormModified) { - e.preventDefault(); - e.returnValue = ''; - } - }); - - const backLinks = document.querySelectorAll('.btn-back'); - backLinks.forEach(link => { - link.addEventListener('click', function(e) { - if (isFormModified) { - const confirmLeave = confirm('Anda memiliki perubahan yang belum disimpan. Yakin ingin keluar?'); - if (!confirmLeave) { - e.preventDefault(); + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.classList.add('opacity-75', 'cursor-not-allowed'); } + if (submitSpinner) submitSpinner.classList.remove('hidden'); + if (submitText) submitText.textContent = 'Menyimpan...'; + }); + } + + window.addEventListener('beforeunload', function(e) { + if (isFormModified) { + e.preventDefault(); + e.returnValue = ''; } }); + + const backLinks = document.querySelectorAll('.btn-back'); + backLinks.forEach(link => { + link.addEventListener('click', function(e) { + if (isFormModified) { + const confirmLeave = confirm('Anda memiliki perubahan yang belum disimpan. Yakin ingin keluar?'); + if (!confirmLeave) { + e.preventDefault(); + } + } + }); + }); }); -}); \ No newline at end of file +})(); \ No newline at end of file diff --git a/public/js/image-cropper.js b/public/js/image-cropper.js new file mode 100644 index 00000000..67f4e2d6 --- /dev/null +++ b/public/js/image-cropper.js @@ -0,0 +1,175 @@ +/** + * Image Cropper - Profile Photo + * Menggunakan Cropper.js untuk crop foto profil berbentuk lingkaran + */ +document.addEventListener('DOMContentLoaded', function () { + const fotoInput = document.getElementById('foto-input'); + const cropModal = document.getElementById('crop-modal'); + const cropImage = document.getElementById('crop-image'); + const cropSaveBtn = document.getElementById('crop-save-btn'); + const cropCancelBtn = document.getElementById('crop-cancel-btn'); + const cropRotateLeftBtn = document.getElementById('crop-rotate-left'); + const cropRotateRightBtn = document.getElementById('crop-rotate-right'); + const cropResetBtn = document.getElementById('crop-reset'); + const cropZoomInBtn = document.getElementById('crop-zoom-in'); + const cropZoomOutBtn = document.getElementById('crop-zoom-out'); + + let cropper = null; + + if (!fotoInput || !cropModal) return; + + fotoInput.addEventListener('change', function (e) { + const file = e.target.files[0]; + if (!file) return; + + if (!file.type.startsWith('image/')) { + alert('File harus berupa gambar.'); + fotoInput.value = ''; + return; + } + + const reader = new FileReader(); + reader.onload = function (event) { + cropImage.src = event.target.result; + cropModal.classList.remove('hidden'); + + if (cropper) { + cropper.destroy(); + } + + setTimeout(function () { + cropper = new Cropper(cropImage, { + aspectRatio: 1, + viewMode: 1, + dragMode: 'move', + autoCropArea: 0.85, + cropBoxResizable: false, + cropBoxMovable: false, + guides: false, + center: true, + highlight: false, + background: false, + responsive: true, + restore: false, + checkCrossOrigin: false, + ready: function () { + const containerData = cropper.getContainerData(); + const size = Math.min(containerData.width, containerData.height) * 0.8; + cropper.setCropBoxData({ + left: (containerData.width - size) / 2, + top: (containerData.height - size) / 2, + width: size, + height: size, + }); + }, + }); + }, 100); + }; + reader.readAsDataURL(file); + }); + + if (cropRotateLeftBtn) { + cropRotateLeftBtn.addEventListener('click', function () { + if (cropper) cropper.rotate(-90); + }); + } + + if (cropRotateRightBtn) { + cropRotateRightBtn.addEventListener('click', function () { + if (cropper) cropper.rotate(90); + }); + } + + if (cropResetBtn) { + cropResetBtn.addEventListener('click', function () { + if (cropper) cropper.reset(); + }); + } + + if (cropZoomInBtn) { + cropZoomInBtn.addEventListener('click', function () { + if (cropper) cropper.zoom(0.1); + }); + } + + if (cropZoomOutBtn) { + cropZoomOutBtn.addEventListener('click', function () { + if (cropper) cropper.zoom(-0.1); + }); + } + + cropSaveBtn.addEventListener('click', function () { + if (!cropper) return; + + cropSaveBtn.disabled = true; + cropSaveBtn.innerHTML = ` + + + + + Memproses... + `; + + const canvas = cropper.getCroppedCanvas({ + width: 512, + height: 512, + fillColor: '#fff', + imageSmoothingEnabled: true, + imageSmoothingQuality: 'high', + }); + + canvas.toBlob(function (blob) { + const croppedFile = new File([blob], 'profile-photo.jpg', { + type: 'image/jpeg', + }); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(croppedFile); + fotoInput.files = dataTransfer.files; + + const preview = document.getElementById('preview-foto'); + const previewNew = document.getElementById('preview-foto-new'); + const placeholder = document.getElementById('placeholder-foto'); + + const url = URL.createObjectURL(blob); + if (preview) { + preview.src = url; + } else { + if (placeholder) placeholder.classList.add('hidden'); + if (previewNew) { + previewNew.src = url; + previewNew.classList.remove('hidden'); + } + } + + closeCropModal(); + }, 'image/jpeg', 0.9); + }); + + cropCancelBtn.addEventListener('click', function () { + fotoInput.value = ''; + closeCropModal(); + }); + + cropModal.addEventListener('click', function (e) { + if (e.target === cropModal) { + fotoInput.value = ''; + closeCropModal(); + } + }); + + function closeCropModal() { + cropModal.classList.add('hidden'); + if (cropper) { + cropper.destroy(); + cropper = null; + } + cropSaveBtn.disabled = false; + cropSaveBtn.innerHTML = ` + + + + Simpan + `; + } +}); diff --git a/public/js/kantor-map.js b/public/js/kantor-map.js index e663ae1a..b3b70ba3 100644 --- a/public/js/kantor-map.js +++ b/public/js/kantor-map.js @@ -1,9 +1,23 @@ (function () { - const defaultLoc = [-7.9797, 112.6304]; - let maps = { create: null, edit: null }; - let markers = { create: null, edit: null }; - let circles = { create: null, edit: null }; - let geocoders = { create: null, edit: null }; + const defaultLoc = { lat: -7.9797, lng: 112.6304 }; + let maps = { create: null, edit: null, detail: null }; + let markers = { create: null, edit: null, detail: null }; + let circles = { create: null, edit: null, detail: null }; + let autocompletes = { create: null, edit: null }; + let geocoder = null; + + function getGeocoder() { + if (!geocoder) geocoder = new google.maps.Geocoder(); + return geocoder; + } + + function reverseGeocode(lat, lng, type) { + getGeocoder().geocode({ location: { lat, lng } }, function (results, status) { + if (status === 'OK' && results[0]) { + setVal(`${type === 'create' ? '' : 'edit-'}alamat`, results[0].formatted_address); + } + }); + } function setVal(id, v) { const el = document.getElementById(id); @@ -12,176 +26,181 @@ function locateUser(type) { if (!navigator.geolocation) { - Swal.fire({ - icon: 'error', - title: 'Tidak Didukung', - text: 'Browser Anda tidak mendukung fitur lokasi.', - confirmButtonColor: '#1e293b', - }); + Swal.fire({ icon: 'error', title: 'Tidak Didukung', text: 'Browser Anda tidak mendukung fitur lokasi.', confirmButtonColor: '#1e293b' }); return; } - - Swal.fire({ - title: 'Mencari Lokasi...', - text: 'Mengambil posisi GPS perangkat Anda', - allowOutsideClick: false, - showConfirmButton: false, - didOpen: () => Swal.showLoading() - }); + + Swal.fire({ title: 'Mencari Lokasi...', text: 'Mengambil posisi GPS perangkat Anda', allowOutsideClick: false, showConfirmButton: false, didOpen: () => Swal.showLoading() }); navigator.geolocation.getCurrentPosition( (pos) => { Swal.close(); const lat = pos.coords.latitude; const lng = pos.coords.longitude; + const position = { lat, lng }; + if (maps[type]) { - maps[type].setView([lat, lng], 16); - if (markers[type]) markers[type].setLatLng([lat, lng]); - if (circles[type]) circles[type].setLatLng([lat, lng]); + maps[type].panTo(position); + maps[type].setZoom(16); + if (markers[type]) markers[type].setPosition(position); + if (circles[type]) circles[type].setCenter(position); setVal(`${type}-lat`, lat.toFixed(6)); setVal(`${type}-long`, lng.toFixed(6)); + reverseGeocode(lat, lng, type); } }, (error) => { Swal.close(); let msg = 'Gagal mendapatkan lokasi.'; - if (error.code === 1) msg = 'Izin lokasi ditolak oleh pengguna. Aktifkan izin lokasi di pengaturan browser.'; + if (error.code === 1) msg = 'Izin lokasi ditolak. Aktifkan di pengaturan browser.'; else if (error.code === 2) msg = 'Posisi tidak dapat ditentukan. Pastikan GPS aktif.'; else if (error.code === 3) msg = 'Waktu permintaan lokasi habis. Coba lagi.'; - - Swal.fire({ - icon: 'warning', - title: 'Lokasi Tidak Ditemukan', - text: msg, - confirmButtonColor: '#1e293b', - confirmButtonText: 'OK' - }); + Swal.fire({ icon: 'warning', title: 'Lokasi Tidak Ditemukan', text: msg, confirmButtonColor: '#1e293b' }); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } - function removeExtraMarkers(type) { - if (!maps[type]) return; - maps[type].eachLayer(function(layer) { - if (layer instanceof L.Marker && layer !== markers[type]) { - maps[type].removeLayer(layer); - } - }); + function getCircleColor(type) { + if (type === 'create') return '#3b82f6'; + if (type === 'detail') return '#1e293b'; + return '#f59e0b'; } function initMap(type, lat, lng, radius) { const container = document.getElementById(`map-${type}`); if (!container) return; + const position = { lat: parseFloat(lat), lng: parseFloat(lng) }; + const isDraggable = type !== 'detail'; + if (maps[type]) { - const pos = [lat, lng]; - maps[type].setView(pos, 15); - if (markers[type]) markers[type].setLatLng(pos); - if (circles[type]) circles[type].setLatLng(pos); - if (circles[type]) circles[type].setRadius(radius); - removeExtraMarkers(type); - setTimeout(() => maps[type].invalidateSize(), 300); + maps[type].panTo(position); + maps[type].setZoom(15); + if (markers[type]) markers[type].setPosition(position); + if (circles[type]) { + circles[type].setCenter(position); + circles[type].setRadius(parseFloat(radius)); + } + google.maps.event.trigger(maps[type], 'resize'); return; } - maps[type] = L.map(`map-${type}`).setView([lat, lng], 15); - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(maps[type]); - - markers[type] = L.marker([lat, lng], { draggable: true }).addTo(maps[type]); - circles[type] = L.circle([lat, lng], { - color: type === 'create' ? '#3b82f6' : (type === 'detail' ? '#1e293b' : '#f59e0b'), + maps[type] = new google.maps.Map(container, { + center: position, + zoom: 15, + mapTypeControl: false, + streetViewControl: false, + fullscreenControl: false, + gestureHandling: 'greedy', + styles: [{ featureType: 'poi', elementType: 'labels', stylers: [{ visibility: 'off' }] }] + }); + + markers[type] = new google.maps.Marker({ + position: position, + map: maps[type], + draggable: isDraggable, + animation: google.maps.Animation.DROP, + }); + + circles[type] = new google.maps.Circle({ + map: maps[type], + center: position, + radius: parseFloat(radius), + fillColor: getCircleColor(type), fillOpacity: 0.1, - radius: radius - }).addTo(maps[type]); + strokeColor: getCircleColor(type), + strokeWeight: 2, + clickable: false, + }); - const update = (e) => { - const latlng = e.target ? e.target.getLatLng() : e.latlng; - if (markers[type]) markers[type].setLatLng([latlng.lat, latlng.lng]); - if (circles[type]) circles[type].setLatLng([latlng.lat, latlng.lng]); - setVal(`${type}-lat`, latlng.lat.toFixed(6)); - setVal(`${type}-long`, latlng.lng.toFixed(6)); - removeExtraMarkers(type); - }; + if (isDraggable) { + markers[type].addListener('dragend', function () { + const pos = markers[type].getPosition(); + circles[type].setCenter(pos); + setVal(`${type}-lat`, pos.lat().toFixed(6)); + setVal(`${type}-long`, pos.lng().toFixed(6)); + reverseGeocode(pos.lat(), pos.lng(), type); + }); - markers[type].on('dragend', update); - maps[type].on('click', update); + maps[type].addListener('click', function (e) { + const pos = e.latLng; + markers[type].setPosition(pos); + circles[type].setCenter(pos); + setVal(`${type}-lat`, pos.lat().toFixed(6)); + setVal(`${type}-long`, pos.lng().toFixed(6)); + reverseGeocode(pos.lat(), pos.lng(), type); + }); - if (typeof L.Control.Geocoder !== 'undefined') { - geocoders[type] = L.Control.geocoder({ - placeholder: 'Cari lokasi...', - errorMessage: 'Lokasi tidak ditemukan', - collapsed: false, - defaultMarkGeocode: false, - geocoder: L.Control.Geocoder.nominatim({ - geocodingQueryParams: { - countrycodes: 'id', - 'accept-language': 'id' + // Google Places Autocomplete (search lokasi) + const searchInput = document.getElementById(`search-${type}`); + if (searchInput && !autocompletes[type]) { + const autocomplete = new google.maps.places.Autocomplete(searchInput, { + fields: ['geometry', 'name', 'formatted_address'], + componentRestrictions: { country: 'id' }, + }); + + autocomplete.bindTo('bounds', maps[type]); + + autocomplete.addListener('place_changed', function () { + const place = autocomplete.getPlace(); + if (!place.geometry || !place.geometry.location) { + console.warn("Tempat tidak ditemukan:", place); + return; } - }) - }).on('markgeocode', function (e) { - const { center } = e.geocode; - maps[type].setView(center, 16); - if (markers[type]) markers[type].setLatLng(center); - if (circles[type]) circles[type].setLatLng(center); - setVal(`${type}-lat`, center.lat.toFixed(6)); - setVal(`${type}-long`, center.lng.toFixed(6)); - - removeExtraMarkers(type); - - setTimeout(() => { - removeExtraMarkers(type); - }, 200); - }).addTo(maps[type]); - } - const locateBtn = L.control({ position: 'topleft' }); - locateBtn.onAdd = function() { - const div = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom'); - div.style.backgroundColor = 'white'; - div.style.width = '30px'; - div.style.height = '30px'; - div.style.display = 'flex'; - div.style.alignItems = 'center'; - div.style.justifyContent = 'center'; - div.style.cursor = 'pointer'; - div.innerHTML = ''; - div.onclick = function(e) { + const loc = place.geometry.location; + maps[type].panTo(loc); + maps[type].setZoom(16); + markers[type].setPosition(loc); + circles[type].setCenter(loc); + setVal(`${type}-lat`, loc.lat().toFixed(6)); + setVal(`${type}-long`, loc.lng().toFixed(6)); + + if (place.formatted_address) { + setVal(`${type === 'create' ? '' : 'edit-'}alamat`, place.formatted_address); + } + }); + + // Cegah Enter submit form + searchInput.addEventListener('keydown', function (e) { + if (e.key === 'Enter') e.preventDefault(); + }); + + autocompletes[type] = autocomplete; + } + + // Tombol "Lokasi Saya" + const locateBtn = document.createElement('button'); + locateBtn.type = 'button'; + locateBtn.innerHTML = ''; + locateBtn.style.cssText = 'background:white;border:none;width:40px;height:40px;display:flex;align-items:center;justify-content:center;cursor:pointer;margin:10px;border-radius:8px;box-shadow:0 2px 6px rgba(0,0,0,0.15);color:#475569;transition:all 0.2s;'; + locateBtn.addEventListener('mouseenter', function () { this.style.background = '#f1f5f9'; }); + locateBtn.addEventListener('mouseleave', function () { this.style.background = 'white'; }); + locateBtn.addEventListener('click', function (e) { + e.preventDefault(); e.stopPropagation(); - locateUser(type); - }; - return div; - }; - locateBtn.addTo(maps[type]); - - setTimeout(() => maps[type].invalidateSize(), 300); + locateUser(type); + }); + maps[type].controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(locateBtn); + } } function setupRadiusListeners() { ['create', 'edit'].forEach(t => { const el = document.getElementById(`${t}-radius`); - if (el) el.addEventListener('input', (e) => circles[t] ? circles[t].setRadius(e.target.value) : null); - }); - } - - function cleanupMaps() { - ['create', 'edit', 'detail'].forEach(t => { - if (maps[t]) { - maps[t].remove(); - maps[t] = null; - markers[t] = null; - circles[t] = null; - geocoders[t] = null; - } + if (el) el.addEventListener('input', (e) => { + if (circles[t]) circles[t].setRadius(parseFloat(e.target.value) || 0); + }); }); } window.addEventListener('open-modal', function (e) { if (e.detail == 'create-kantor') { - setVal('create-lat', defaultLoc[0]); - setVal('create-long', defaultLoc[1]); + setVal('create-lat', defaultLoc.lat); + setVal('create-long', defaultLoc.lng); setTimeout(() => { - initMap('create', defaultLoc[0], defaultLoc[1], 50); + initMap('create', defaultLoc.lat, defaultLoc.lng, 50); locateUser('create'); }, 300); } @@ -205,7 +224,7 @@ setTimeout(() => initMap('edit', parseFloat(d.lat), parseFloat(d.long), parseInt(d.radius)), 300); }; - window.openDetailModal = function(btn) { + window.openDetailModal = function (btn) { const data = btn.dataset; document.getElementById('detail-nama').innerText = data.nama; document.getElementById('detail-tipe').innerText = data.tipe; @@ -214,14 +233,11 @@ document.getElementById('detail-radius').innerText = data.radius + ' m'; document.getElementById('detail-alamat').innerText = data.alamat || '-'; - window.dispatchEvent(new CustomEvent('open-modal', { - detail: 'detail-kantor' - })); - + window.dispatchEvent(new CustomEvent('open-modal', { detail: 'detail-kantor' })); setTimeout(() => { initMap('detail', parseFloat(data.lat), parseFloat(data.long), parseInt(data.radius)); }, 300); - } + }; setupRadiusListeners(); @@ -229,7 +245,12 @@ window.__kantorTurboAttached = true; document.addEventListener('turbo:before-cache', function () { - cleanupMaps(); + ['create', 'edit', 'detail'].forEach(t => { + maps[t] = null; + markers[t] = null; + circles[t] = null; + autocompletes[t] = null; + }); }); document.addEventListener('turbo:load', function () { diff --git a/resources/views/kantor/index.blade.php b/resources/views/kantor/index.blade.php index 3054cb30..c030a5b7 100644 --- a/resources/views/kantor/index.blade.php +++ b/resources/views/kantor/index.blade.php @@ -3,29 +3,143 @@ @section('title', 'Data Kantor') @section('style') - - @endsection @section('script') - - + - + @endsection @section('content') @@ -60,10 +174,13 @@ class="px-5 py-2.5 bg-primary text-white text-sm font-semibold rounded-xl hover: - - {{ number_format($k->latitude, 5) }}, {{ number_format($k->longitude, 5) }} - + + + + + + {{ number_format($k->latitude, 5) }}, {{ number_format($k->longitude, 5) }} + @@ -108,11 +225,24 @@ class="p-2 bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-200 transition" + {{-- Modal Tambah Kantor --}}
@csrf
- + +
+ + + + +
@@ -123,8 +253,8 @@ class="p-2 bg-slate-100 text-slate-600 rounded-lg hover:bg-slate-200 transition"
-
-
+
+
@@ -137,10 +267,17 @@ class="px-4 py-2 text-sm bg-primary text-white rounded-xl hover:bg-primary/90">S
+ {{-- Modal Edit Kantor --}}
@csrf @method('PUT')
+
+ + + + +
@@ -151,8 +288,8 @@ class="px-4 py-2 text-sm bg-primary text-white rounded-xl hover:bg-primary/90">S
-
-
+
+
@@ -164,38 +301,44 @@ class="px-4 py-2 text-sm bg-amber-500 text-white rounded-xl hover:bg-amber-600">
+ + {{-- Modal Detail Kantor --}} -
-
-
-
- -

+
+
+ +
+
+
Nama Kantor
+

-
- -

+
+
Tipe Kantor
+

-
-
- -

+ +
+
+
Latitude
+

-
- -

+
+
Longitude
+

-
- -

+
+
Radius
+

-
- -

+ +
+
Alamat Lengkap
+

-
+ +
diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php index 206316bf..2d38d73c 100644 --- a/resources/views/profile/edit.blade.php +++ b/resources/views/profile/edit.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.app') +@extends('layouts.app') @section('title', 'Pengaturan Akun') @@ -45,8 +45,7 @@ class="absolute bottom-0 right-0 p-2 bg-white rounded-full shadow-lg border bord d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"> - +

{{ auth()->user()->nama_lengkap }}

@@ -86,27 +85,121 @@ class="px-6 py-2.5 bg-primary text-white font-semibold rounded-xl hover:bg-prima
- + + + {{-- Cropper circular overlay --}} + @endsection