diff --git a/Views/Main/Profil/Edit.cshtml b/Views/Main/Profil/Edit.cshtml
index dbf9b18..f6508b0 100644
--- a/Views/Main/Profil/Edit.cshtml
+++ b/Views/Main/Profil/Edit.cshtml
@@ -1,7 +1,12 @@
-ο»Ώ@{
+@{
ViewData["Title"] = "Profil Bank Sampah";
}
+@section Styles {
+
+
+}
+
+
+
+
+ @{
+ ViewData["MapId"] = "map-picker";
+ ViewData["LatInputId"] = "latitude-input";
+ ViewData["LngInputId"] = "longitude-input";
+ ViewData["SearchInputId"] = "search-location";
+ ViewData["SearchResultsId"] = "search-results";
+ ViewData["ClearSearchBtnId"] = "clear-search";
+ ViewData["Label"] = "Pilih Lokasi di Peta";
+ }
+ @await Html.PartialAsync("_LeafletMapPicker")
+
-
\ No newline at end of file
+
+
+@section Scripts {
+
+
+
+}
\ No newline at end of file
diff --git a/Views/Main/Profil/Index.cshtml b/Views/Main/Profil/Index.cshtml
index 82a60a5..714abc4 100644
--- a/Views/Main/Profil/Index.cshtml
+++ b/Views/Main/Profil/Index.cshtml
@@ -2,6 +2,17 @@
ViewData["Title"] = "Profil Bank Sampah";
}
+@section Styles {
+
+
+}
+
@@ -149,12 +160,55 @@
Latitude
- 324242
+ -6.2088
Longitude
- -371872
+ 106.8456
+
+
+
+
+
+
+
+ Lokasi di Peta
+
+
+
+
+ Peta menampilkan lokasi Bank Sampah
+
+
-
\ No newline at end of file
+
+
+@section Scripts {
+
+
+}
\ No newline at end of file
diff --git a/Views/Shared/_LeafletMapPicker.cshtml b/Views/Shared/_LeafletMapPicker.cshtml
new file mode 100644
index 0000000..6608dbe
--- /dev/null
+++ b/Views/Shared/_LeafletMapPicker.cshtml
@@ -0,0 +1,78 @@
+@*
+ Leaflet Map Picker - Reusable Partial View
+
+ Usage:
+ @await Html.PartialAsync("_LeafletMapPicker", new LeafletMapPickerModel
+ {
+ MapId = "map-picker",
+ LatInputId = "latitude-input",
+ LngInputId = "longitude-input",
+ SearchInputId = "search-location",
+ SearchResultsId = "search-results",
+ ClearSearchBtnId = "clear-search",
+ Label = "Pilih Lokasi di Peta",
+ HelpText = "Cari lokasi menggunakan search box di atas, atau klik pada peta untuk menentukan lokasi secara manual"
+ })
+*@
+
+@model dynamic
+
+@{
+ var mapId = ViewData["MapId"]?.ToString() ?? "map-picker";
+ var latInputId = ViewData["LatInputId"]?.ToString() ?? "latitude-input";
+ var lngInputId = ViewData["LngInputId"]?.ToString() ?? "longitude-input";
+ var searchInputId = ViewData["SearchInputId"]?.ToString() ?? "search-location";
+ var searchResultsId = ViewData["SearchResultsId"]?.ToString() ?? "search-results";
+ var clearSearchBtnId = ViewData["ClearSearchBtnId"]?.ToString() ?? "clear-search";
+ var label = ViewData["Label"]?.ToString() ?? "Pilih Lokasi di Peta";
+ var helpText = ViewData["HelpText"]?.ToString() ?? "Cari lokasi menggunakan search box di atas, atau klik pada peta untuk menentukan lokasi secara manual";
+ var showLabel = ViewData["ShowLabel"] == null ? true : (ViewData["ShowLabel"] is bool labelValue && labelValue);
+ var showSearch = ViewData["ShowSearch"] == null ? true : (ViewData["ShowSearch"] is bool searchValue && searchValue);
+ var showHelpText = ViewData["ShowHelpText"] == null ? true : (ViewData["ShowHelpText"] is bool helpValue && helpValue);
+}
+
+
+ @if (showLabel)
+ {
+
+ }
+
+ @if (showSearch)
+ {
+
+
+ }
+
+
+
+ @if (showHelpText)
+ {
+
+
+ @helpText
+
+ }
+
diff --git a/wwwroot/css/leaflet-map-picker.css b/wwwroot/css/leaflet-map-picker.css
new file mode 100644
index 0000000..ea6a489
--- /dev/null
+++ b/wwwroot/css/leaflet-map-picker.css
@@ -0,0 +1,107 @@
+/**
+ * Leaflet Map Picker - Reusable Styles
+ */
+
+.leaflet-map-picker-container {
+ width: 100%;
+}
+
+.leaflet-map-picker {
+ height: 400px;
+ width: 100%;
+ cursor: crosshair;
+ border-radius: 0.5rem;
+}
+
+.leaflet-map-picker-search {
+ position: relative;
+ margin-bottom: 1rem;
+}
+
+.leaflet-map-picker-search-input {
+ width: 100%;
+ padding-left: 2.5rem;
+}
+
+.leaflet-map-picker-search-icon {
+ position: absolute;
+ left: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #9ca3af;
+}
+
+.leaflet-map-picker-clear-btn {
+ position: absolute;
+ right: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #9ca3af;
+ transition: color 0.2s;
+}
+
+.leaflet-map-picker-clear-btn:hover {
+ color: #4b5563;
+}
+
+.leaflet-map-picker-results {
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin-top: 0.25rem;
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.5rem;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
+ max-height: 16rem;
+ overflow-y: auto;
+ z-index: 9999;
+}
+
+.leaflet-map-picker-results.hidden {
+ display: none;
+}
+
+.leaflet-map-picker-result-item {
+ padding: 0.75rem;
+ cursor: pointer;
+ border-bottom: 1px solid #f3f4f6;
+ transition: background-color 0.2s;
+}
+
+.leaflet-map-picker-result-item:last-child {
+ border-bottom: none;
+}
+
+.leaflet-map-picker-result-item:hover {
+ background-color: #f9fafb;
+}
+
+.leaflet-map-picker-help-text {
+ font-size: 0.75rem;
+ color: #6b7280;
+ margin-top: 0.5rem;
+}
+
+/* Loading spinner */
+.leaflet-map-picker-spinner {
+ width: 1.25rem;
+ height: 1.25rem;
+ border: 2px solid currentColor;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: leaflet-map-picker-spin 0.6s linear infinite;
+}
+
+@keyframes leaflet-map-picker-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .leaflet-map-picker {
+ height: 300px;
+ }
+}
diff --git a/wwwroot/js/leaflet-map-picker.README.md b/wwwroot/js/leaflet-map-picker.README.md
new file mode 100644
index 0000000..376756f
--- /dev/null
+++ b/wwwroot/js/leaflet-map-picker.README.md
@@ -0,0 +1,309 @@
+# Leaflet Map Picker - Dokumentasi
+
+Komponen reusable untuk memilih lokasi menggunakan Leaflet Maps dengan fitur pencarian dan reverse geocoding.
+
+## Fitur
+
+- πΊοΈ Interactive map dengan OpenStreetMap
+- π Pencarian lokasi dengan Nominatim API
+- π Click-to-select koordinat
+- π Reverse geocoding untuk mendapatkan alamat otomatis
+- π± Responsive design
+- β‘ Mudah dikonfigurasi dan reusable
+
+## Instalasi
+
+### 1. Include CSS dan JavaScript
+
+Tambahkan di section `Styles` dan `Scripts` pada view Anda:
+
+```cshtml
+@section Styles {
+
+
+}
+
+@section Scripts {
+
+
+}
+```
+
+## Penggunaan
+
+### Metode 1: Menggunakan Partial View (Recommended)
+
+#### Langkah 1: Tambahkan Input Fields untuk Latitude dan Longitude
+
+```cshtml
+
+
+
+```
+
+#### Langkah 2: Include Partial View
+
+```cshtml
+
+ @{
+ ViewData["MapId"] = "map-picker";
+ ViewData["LatInputId"] = "latitude-input";
+ ViewData["LngInputId"] = "longitude-input";
+ ViewData["SearchInputId"] = "search-location";
+ ViewData["SearchResultsId"] = "search-results";
+ ViewData["ClearSearchBtnId"] = "clear-search";
+ ViewData["Label"] = "Pilih Lokasi di Peta";
+ }
+ @await Html.PartialAsync("_LeafletMapPicker")
+
+```
+
+#### Langkah 3: Initialize JavaScript Component
+
+```cshtml
+@section Scripts {
+
+
+
+}
+```
+
+### Metode 2: HTML Manual
+
+```html
+
+
+
+
+
+
+
+
+
+ Cari lokasi menggunakan search box di atas, atau klik pada peta untuk menentukan lokasi secara manual
+
+
+```
+
+## Konfigurasi Options
+
+| Option | Type | Default | Deskripsi |
+|--------|------|---------|-----------|
+| `mapElementId` | string | `'map-picker'` | ID elemen untuk map container |
+| `latInputId` | string | `'latitude-input'` | ID input field untuk latitude |
+| `lngInputId` | string | `'longitude-input'` | ID input field untuk longitude |
+| `searchInputId` | string | `'search-location'` | ID input field untuk search box |
+| `searchResultsId` | string | `'search-results'` | ID elemen untuk search results dropdown |
+| `clearSearchBtnId` | string | `'clear-search'` | ID button untuk clear search |
+| `alamatInputSelector` | string | `null` | CSS selector untuk input alamat (optional) |
+| `initialLat` | number | `-6.2088` | Latitude awal map (Jakarta) |
+| `initialLng` | number | `106.8456` | Longitude awal map (Jakarta) |
+| `initialZoom` | number | `12` | Zoom level awal |
+| `maxZoom` | number | `18` | Maximum zoom level |
+| `minZoom` | number | `4` | Minimum zoom level |
+| `searchDebounceMs` | number | `500` | Debounce delay untuk search (ms) |
+| `countryCode` | string | `'id'` | Country code untuk filter pencarian |
+| `language` | string | `'id'` | Language untuk API results |
+
+## ViewData Options untuk Partial View
+
+| Key | Type | Default | Deskripsi |
+|-----|------|---------|-----------|
+| `MapId` | string | `'map-picker'` | ID untuk map element |
+| `LatInputId` | string | `'latitude-input'` | ID untuk latitude input |
+| `LngInputId` | string | `'longitude-input'` | ID untuk longitude input |
+| `SearchInputId` | string | `'search-location'` | ID untuk search input |
+| `SearchResultsId` | string | `'search-results'` | ID untuk search results |
+| `ClearSearchBtnId` | string | `'clear-search'` | ID untuk clear button |
+| `Label` | string | `'Pilih Lokasi di Peta'` | Label text |
+| `HelpText` | string | Default help text | Help text di bawah map |
+| `ShowLabel` | bool | `true` | Tampilkan label |
+| `ShowSearch` | bool | `true` | Tampilkan search box |
+| `ShowHelpText` | bool | `true` | Tampilkan help text |
+
+## Public API Methods
+
+### `getCoordinates()`
+Mendapatkan koordinat saat ini.
+
+```javascript
+const coords = leafletMapPickerInstance.getCoordinates();
+console.log(coords); // { lat: -6.2088, lng: 106.8456 }
+```
+
+### `setCoordinates(lat, lng, updateMarker = true)`
+Set koordinat secara programmatic.
+
+```javascript
+leafletMapPickerInstance.setCoordinates(-6.2088, 106.8456, true);
+```
+
+### `destroy()`
+Clean up instance dan event listeners.
+
+```javascript
+leafletMapPickerInstance.destroy();
+```
+
+## Contoh Lengkap
+
+```cshtml
+@{
+ ViewData["Title"] = "Edit Lokasi";
+}
+
+@section Styles {
+
+
+}
+
+
+
+@section Scripts {
+
+
+
+}
+```
+
+## Custom Styling
+
+Anda bisa override CSS classes untuk custom styling:
+
+```css
+/* Custom map height */
+.leaflet-map-picker {
+ height: 500px;
+}
+
+/* Custom search box styling */
+.leaflet-map-picker-search-input {
+ border-color: #your-color;
+}
+
+/* Custom search results */
+.leaflet-map-picker-result-item:hover {
+ background-color: #your-color;
+}
+```
+
+## Browser Support
+
+- Chrome (latest)
+- Firefox (latest)
+- Safari (latest)
+- Edge (latest)
+
+## Dependencies
+
+- Leaflet 1.9.4+
+- OpenStreetMap (Tile Layer)
+- Nominatim API (Geocoding)
+
+## Troubleshooting
+
+### Map tidak muncul
+- Pastikan Leaflet CSS dan JS sudah ter-load
+- Periksa console untuk error
+- Pastikan element ID sudah benar
+
+### Search tidak bekerja
+- Pastikan ada koneksi internet
+- Nominatim API memiliki rate limit
+- Periksa browser console untuk error
+
+### Koordinat tidak update
+- Pastikan input ID sudah benar
+- Periksa attribute `step="any"` pada input number
+
+## License
+
+MIT License
+
+## Author
+
+Bank Sampah Development Team
diff --git a/wwwroot/js/leaflet-map-picker.js b/wwwroot/js/leaflet-map-picker.js
new file mode 100644
index 0000000..629b147
--- /dev/null
+++ b/wwwroot/js/leaflet-map-picker.js
@@ -0,0 +1,423 @@
+/**
+ * Leaflet Map Picker - Reusable Component
+ *
+ * Usage:
+ * const mapPicker = new LeafletMapPicker({
+ * mapElementId: 'map-picker',
+ * latInputId: 'latitude-input',
+ * lngInputId: 'longitude-input',
+ * searchInputId: 'search-location',
+ * searchResultsId: 'search-results',
+ * clearSearchBtnId: 'clear-search',
+ * alamatInputSelector: 'textarea[name="alamat_lengkap"]',
+ * initialLat: -6.2088,
+ * initialLng: 106.8456,
+ * initialZoom: 12
+ * });
+ */
+
+class LeafletMapPicker {
+ constructor(options) {
+ // Default options
+ this.options = {
+ mapElementId: 'map-picker',
+ latInputId: 'latitude-input',
+ lngInputId: 'longitude-input',
+ searchInputId: 'search-location',
+ searchResultsId: 'search-results',
+ clearSearchBtnId: 'clear-search',
+ alamatInputSelector: null,
+ initialLat: -6.2088,
+ initialLng: 106.8456,
+ initialZoom: 12,
+ maxZoom: 18,
+ minZoom: 4,
+ searchDebounceMs: 500,
+ countryCode: 'id',
+ language: 'id',
+ ...options
+ };
+
+ // State
+ this.currentMarker = null;
+ this.searchTimeout = null;
+
+ // Initialize
+ this.init();
+ }
+
+ init() {
+ // Get DOM elements
+ this.mapElement = document.getElementById(this.options.mapElementId);
+ this.latInput = document.getElementById(this.options.latInputId);
+ this.lngInput = document.getElementById(this.options.lngInputId);
+ this.searchInput = document.getElementById(this.options.searchInputId);
+ this.searchResults = document.getElementById(this.options.searchResultsId);
+ this.clearSearchBtn = document.getElementById(this.options.clearSearchBtnId);
+ this.alamatInput = this.options.alamatInputSelector
+ ? document.querySelector(this.options.alamatInputSelector)
+ : null;
+
+ if (!this.mapElement) {
+ console.error(`Map element with id "${this.options.mapElementId}" not found`);
+ return;
+ }
+
+ // Initialize map
+ this.initMap();
+
+ // Bind events
+ this.bindEvents();
+
+ // Initialize marker if coordinates already exist
+ this.initializeExistingMarker();
+ }
+
+ initMap() {
+ // Initialize map
+ this.map = L.map(this.mapElement).setView(
+ [this.options.initialLat, this.options.initialLng],
+ this.options.initialZoom
+ );
+
+ // Add tile layer
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: 'Β© OpenStreetMap contributors',
+ maxZoom: this.options.maxZoom,
+ minZoom: this.options.minZoom
+ }).addTo(this.map);
+
+ // Handle map click
+ this.map.on('click', (e) => this.onMapClick(e));
+ }
+
+ bindEvents() {
+ // Search input events
+ if (this.searchInput) {
+ this.searchInput.addEventListener('input', (e) => this.onSearchInput(e));
+ this.searchInput.addEventListener('keydown', (e) => this.onSearchKeydown(e));
+ }
+
+ // Clear search button
+ if (this.clearSearchBtn) {
+ this.clearSearchBtn.addEventListener('click', () => this.clearSearch());
+ }
+
+ // Manual coordinate input events
+ if (this.latInput) {
+ this.latInput.addEventListener('change', () => this.onCoordinateChange());
+ }
+
+ if (this.lngInput) {
+ this.lngInput.addEventListener('change', () => this.onCoordinateChange());
+ }
+
+ // Close search results when clicking outside
+ document.addEventListener('click', (e) => {
+ if (this.searchInput && this.searchResults &&
+ !this.searchInput.contains(e.target) &&
+ !this.searchResults.contains(e.target)) {
+ this.searchResults.classList.add('hidden');
+ }
+ });
+ }
+
+ initializeExistingMarker() {
+ window.addEventListener('load', () => {
+ const lat = parseFloat(this.latInput?.value);
+ const lng = parseFloat(this.lngInput?.value);
+
+ if (!isNaN(lat) && !isNaN(lng)) {
+ this.updateMarker(lat, lng);
+ }
+ });
+ }
+
+ onMapClick(e) {
+ const lat = e.latlng.lat;
+ const lng = e.latlng.lng;
+
+ // Update form inputs
+ if (this.latInput) this.latInput.value = lat.toFixed(8);
+ if (this.lngInput) this.lngInput.value = lng.toFixed(8);
+
+ // Update marker and fetch address
+ this.updateMarker(lat, lng, true);
+ }
+
+ onSearchInput(e) {
+ const query = e.target.value.trim();
+
+ // Show/hide clear button
+ if (this.clearSearchBtn) {
+ if (query.length > 0) {
+ this.clearSearchBtn.classList.remove('hidden');
+ } else {
+ this.clearSearchBtn.classList.add('hidden');
+ if (this.searchResults) {
+ this.searchResults.classList.add('hidden');
+ }
+ }
+ }
+
+ // Debounce search
+ clearTimeout(this.searchTimeout);
+ this.searchTimeout = setTimeout(() => {
+ this.searchLocation(query);
+ }, this.options.searchDebounceMs);
+ }
+
+ onSearchKeydown(e) {
+ // Prevent form submission on Enter
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ }
+ }
+
+ onCoordinateChange() {
+ const lat = parseFloat(this.latInput?.value);
+ const lng = parseFloat(this.lngInput?.value);
+
+ if (!isNaN(lat) && !isNaN(lng)) {
+ this.updateMarker(lat, lng);
+ }
+ }
+
+ async searchLocation(query) {
+ if (!this.searchResults || query.length < 3) {
+ if (this.searchResults) {
+ this.searchResults.classList.add('hidden');
+ }
+ return;
+ }
+
+ // Show loading state
+ this.searchResults.innerHTML = `
+
+ `;
+ this.searchResults.classList.remove('hidden');
+
+ try {
+ const response = await fetch(
+ `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&countrycodes=${this.options.countryCode}&limit=5&addressdetails=1`,
+ {
+ headers: {
+ 'Accept-Language': this.options.language
+ }
+ }
+ );
+ const data = await response.json();
+
+ this.displaySearchResults(data);
+ } catch (error) {
+ console.error('Error searching location:', error);
+ this.searchResults.innerHTML = `
+
+
+ Gagal mencari lokasi. Silakan coba lagi.
+
+ `;
+ }
+ }
+
+ displaySearchResults(results) {
+ if (!this.searchResults) return;
+
+ if (results.length === 0) {
+ this.searchResults.innerHTML = `
+
+
+ Lokasi tidak ditemukan
+
+ `;
+ this.searchResults.classList.remove('hidden');
+ return;
+ }
+
+ this.searchResults.innerHTML = results.map(result => {
+ const escapedDisplayName = result.display_name.replace(/'/g, "\\'");
+ return `
+
+
+
+
+
${result.display_name}
+
+ ${parseFloat(result.lat).toFixed(6)}, ${parseFloat(result.lon).toFixed(6)}
+
+
+
+
+ `;
+ }).join('');
+
+ this.searchResults.classList.remove('hidden');
+ }
+
+ selectSearchResult(lat, lon, displayName) {
+ // Update coordinates
+ if (this.latInput) this.latInput.value = parseFloat(lat).toFixed(8);
+ if (this.lngInput) this.lngInput.value = parseFloat(lon).toFixed(8);
+
+ // Update marker
+ this.updateMarker(lat, lon, true);
+
+ // Clear search
+ if (this.searchResults) this.searchResults.classList.add('hidden');
+ if (this.searchInput) this.searchInput.value = '';
+ if (this.clearSearchBtn) this.clearSearchBtn.classList.add('hidden');
+ }
+
+ clearSearch() {
+ if (this.searchInput) this.searchInput.value = '';
+ if (this.searchResults) this.searchResults.classList.add('hidden');
+ if (this.clearSearchBtn) this.clearSearchBtn.classList.add('hidden');
+ if (this.searchInput) this.searchInput.focus();
+ }
+
+ async fetchAddress(lat, lng) {
+ if (!this.alamatInput) return;
+
+ // Show loading state
+ const originalPlaceholder = this.alamatInput.placeholder;
+ this.alamatInput.placeholder = 'Mengambil alamat dari koordinat...';
+ this.alamatInput.disabled = true;
+
+ try {
+ const response = await fetch(
+ `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1`,
+ {
+ headers: {
+ 'Accept-Language': this.options.language
+ }
+ }
+ );
+ const data = await response.json();
+
+ if (data && data.display_name) {
+ // Build formatted address
+ const addr = data.address || {};
+ let formattedAddress = '';
+
+ // Try to build a structured address
+ if (addr.road || addr.suburb || addr.village) {
+ const parts = [];
+ if (addr.road) parts.push(addr.road);
+ if (addr.house_number) parts.push('No. ' + addr.house_number);
+ if (addr.suburb || addr.village) parts.push(addr.suburb || addr.village);
+ if (addr.city || addr.town || addr.city_district) {
+ parts.push(addr.city || addr.town || addr.city_district);
+ }
+ if (addr.state) parts.push(addr.state);
+ if (addr.postcode) parts.push(addr.postcode);
+
+ formattedAddress = parts.join(', ');
+ } else {
+ // Fallback to display_name
+ formattedAddress = data.display_name;
+ }
+
+ this.alamatInput.value = formattedAddress;
+ this.alamatInput.placeholder = originalPlaceholder;
+
+ // Show success feedback
+ this.alamatInput.classList.add('border-success');
+ setTimeout(() => this.alamatInput.classList.remove('border-success'), 2000);
+ }
+ } catch (error) {
+ console.error('Error fetching address:', error);
+ this.alamatInput.placeholder = 'Gagal mengambil alamat, silakan isi manual...';
+ setTimeout(() => this.alamatInput.placeholder = originalPlaceholder, 3000);
+ } finally {
+ this.alamatInput.disabled = false;
+ }
+ }
+
+ updateMarker(lat, lng, fetchAddr = false) {
+ // Remove existing marker
+ if (this.currentMarker) {
+ this.map.removeLayer(this.currentMarker);
+ }
+
+ // Add new draggable marker
+ this.currentMarker = L.marker([lat, lng], {
+ draggable: true
+ }).addTo(this.map);
+
+ this.currentMarker.bindPopup(`
+
+ Lokasi Dipilih
+ ${lat.toFixed(8)}, ${lng.toFixed(8)}
+ Drag marker untuk mengubah lokasi
+
+ `).openPopup();
+
+ // Handle marker drag
+ this.currentMarker.on('dragend', (e) => {
+ const marker = e.target;
+ const position = marker.getLatLng();
+
+ // Update input values
+ if (this.latInput) this.latInput.value = position.lat.toFixed(8);
+ if (this.lngInput) this.lngInput.value = position.lng.toFixed(8);
+
+ // Update popup content
+ marker.setPopupContent(`
+
+ Lokasi Dipilih
+ ${position.lat.toFixed(8)}, ${position.lng.toFixed(8)}
+ Drag marker untuk mengubah lokasi
+
+ `);
+
+ // Fetch address for new position
+ this.fetchAddress(position.lat, position.lng);
+ });
+
+ // Center map on marker
+ this.map.setView([lat, lng], 15);
+
+ // Fetch address if requested
+ if (fetchAddr) {
+ this.fetchAddress(lat, lng);
+ }
+ }
+
+ // Public API methods
+ getCoordinates() {
+ return {
+ lat: parseFloat(this.latInput?.value),
+ lng: parseFloat(this.lngInput?.value)
+ };
+ }
+
+ setCoordinates(lat, lng, updateMarker = true) {
+ if (this.latInput) this.latInput.value = lat.toFixed(8);
+ if (this.lngInput) this.lngInput.value = lng.toFixed(8);
+
+ if (updateMarker) {
+ this.updateMarker(lat, lng);
+ }
+ }
+
+ destroy() {
+ // Clean up event listeners and map
+ if (this.map) {
+ this.map.remove();
+ }
+ clearTimeout(this.searchTimeout);
+ }
+}
+
+// Export for use in modules or expose globally
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = LeafletMapPicker;
+} else {
+ window.LeafletMapPicker = LeafletMapPicker;
+}