ECOMMERCE/resources/views/components/address/address-map.blade.php

439 lines
19 KiB
PHP

@props([
'latitude' => null,
'longitude' => null,
'latitudeInputName' => 'latitude',
'longitudeInputName' => 'longitude',
'searchPlaceholder' => 'Search for location',
'searchButtonText' => 'Search',
'mapLabel' => 'Select location on map',
'instructionText' => 'Drag the marker to adjust the exact location',
'typeToSearchText' => 'Start typing to search for locations...',
])
<div class="address-map-component">
<!-- Hidden inputs for latitude and longitude -->
<input type="hidden" name="{{ $latitudeInputName }}" id="{{ $latitudeInputName }}" value="{{ $latitude ?? '' }}"
class="address-latitude-input" step="0.000001">
<input type="hidden" name="{{ $longitudeInputName }}" id="{{ $longitudeInputName }}" value="{{ $longitude ?? '' }}"
class="address-longitude-input" step="0.000001">
<!-- Map interface -->
<div class="position-relative">
<label class="form-label">{{ $mapLabel }}</label>
<div class="mb-3 position-relative">
<div class="input-group">
<input type="text" class="form-control address-location-search"
placeholder="{{ $searchPlaceholder }}" autocomplete="off">
<button class="btn btn-outline-secondary address-search-btn" type="button">
<i class="ci-search"></i> {{ $searchButtonText }}
</button>
</div>
<!-- Search suggestions dropdown -->
<div class="address-search-suggestions position-absolute w-100 bg-white border rounded-top-0 rounded-bottom shadow-sm"
style="z-index: 1050; max-height: 200px; overflow-y: auto; display: none;">
<div class="p-2 text-muted small">{{ $typeToSearchText }}</div>
</div>
</div>
<div class="address-map-container" style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;">
</div>
<small class="text-muted">{{ $instructionText }}</small>
</div>
</div>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize all address map components
const addressMapComponents = document.querySelectorAll('.address-map-component');
addressMapComponents.forEach(function(component, index) {
initializeAddressMap(component, index);
});
// Initialize maps from placeholders (for dynamically generated content)
function initializeMapFromPlaceholder(placeholder) {
const component = createMapComponentFromPlaceholder(placeholder);
if (component) {
initializeAddressMap(component, Date.now());
}
}
function createMapComponentFromPlaceholder(placeholder) {
// Get data attributes from placeholder
const latitude = placeholder.dataset.latitude;
const longitude = placeholder.dataset.longitude;
const latitudeInputName = placeholder.dataset.latitudeInput || 'latitude';
const longitudeInputName = placeholder.dataset.longitudeInput || 'longitude';
const searchPlaceholder = placeholder.dataset.searchPlaceholder || 'Search for location';
const searchButtonText = placeholder.dataset.searchButton || 'Search';
const mapLabel = placeholder.dataset.mapLabel || 'Select location on map';
const instructionText = placeholder.dataset.instructionText ||
'Drag the marker to adjust the exact location';
const typeToSearchText = placeholder.dataset.typeToSearch ||
'Start typing to search for locations...';
// Create component HTML
const componentHtml = `
<div class="address-map-component">
<!-- Hidden inputs for latitude and longitude -->
<input type="hidden"
name="${latitudeInputName}"
id="${latitudeInputName}"
value="${latitude || ''}"
class="address-latitude-input"
step="0.000001">
<input type="hidden"
name="${longitudeInputName}"
id="${longitudeInputName}"
value="${longitude || ''}"
class="address-longitude-input"
step="0.000001">
<!-- Map interface -->
<div class="position-relative">
<label class="form-label">${mapLabel}</label>
<div class="mb-3 position-relative">
<div class="input-group">
<input type="text"
class="form-control address-location-search"
placeholder="${searchPlaceholder}"
autocomplete="off">
<button class="btn btn-outline-secondary address-search-btn" type="button">
<i class="ci-search"></i> ${searchButtonText}
</button>
</div>
<!-- Search suggestions dropdown -->
<div class="address-search-suggestions position-absolute w-100 bg-white border rounded-top-0 rounded-bottom shadow-sm"
style="z-index: 1050; max-height: 200px; overflow-y: auto; display: none;">
<div class="p-2 text-muted small">${typeToSearchText}</div>
</div>
</div>
<div class="address-map-container" style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;"></div>
<small class="text-muted">${instructionText}</small>
</div>
</div>
`;
// Replace placeholder with component
placeholder.outerHTML = componentHtml;
// Return the new component element
return placeholder.parentElement.querySelector('.address-map-component');
}
function initializeAddressMap(component, componentIndex) {
let map = null;
let marker = null;
let searchTimeout;
const mapContainer = component.querySelector('.address-map-container');
const searchInput = component.querySelector('.address-location-search');
const searchBtn = component.querySelector('.address-search-btn');
const suggestionsDiv = component.querySelector('.address-search-suggestions');
const latInput = component.querySelector('.address-latitude-input');
const lngInput = component.querySelector('.address-longitude-input');
// Generate unique IDs for this component
const mapId = 'address-map-' + componentIndex;
mapContainer.id = mapId;
// Initialize map
function initializeMap() {
if (typeof L !== 'undefined' && mapContainer) {
// Get initial coordinates or default to Jakarta
let initialLat = -6.2088;
let initialLng = 106.8456;
let initialZoom = 13;
if (latInput.value && lngInput.value) {
const lat = parseFloat(latInput.value);
const lng = parseFloat(lngInput.value);
if (!isNaN(lat) && !isNaN(lng)) {
initialLat = lat;
initialLng = lng;
initialZoom = 15;
}
}
// Initialize map
map = L.map(mapId).setView([initialLat, initialLng], initialZoom);
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: ' OpenStreetMap contributors'
}).addTo(map);
// Add initial marker if coordinates exist
if (initialLat !== -6.2088 || initialLng !== 106.8456) {
marker = L.marker([initialLat, initialLng], {
draggable: true
}).addTo(map);
// Add drag event listener
marker.on('dragend', function(e) {
const position = e.target.getLatLng();
latInput.value = position.lat.toFixed(6);
lngInput.value = position.lng.toFixed(6);
});
} else {
// Add draggable marker at center
marker = L.marker([-6.2088, 106.8456], {
draggable: true
}).addTo(map);
// Add drag event listener
marker.on('dragend', function(e) {
const position = e.target.getLatLng();
latInput.value = position.lat.toFixed(6);
lngInput.value = position.lng.toFixed(6);
});
}
// Add click event to place marker
map.on('click', function(e) {
const position = e.latlng;
if (marker) {
marker.setLatLng(position);
} else {
marker = L.marker(position, {
draggable: true
}).addTo(map);
// Add drag event listener
marker.on('dragend', function(e) {
const pos = e.target.getLatLng();
latInput.value = pos.lat.toFixed(6);
lngInput.value = pos.lng.toFixed(6);
});
}
latInput.value = position.lat.toFixed(6);
lngInput.value = position.lng.toFixed(6);
});
}
}
// Search functionality
function searchLocation(query) {
if (!query) {
console.log('Empty query, skipping search');
return;
}
// Ensure map is initialized
if (!map) {
console.log('Map not initialized, trying to initialize...');
initializeMap();
// Wait a bit for map to initialize
setTimeout(() => {
if (map) {
performSearch(query);
} else {
console.error('Map still not initialized after retry');
}
}, 500);
return;
}
performSearch(query);
}
function performSearch(query) {
console.log('Performing search for:', query);
// Using Nominatim API for geocoding
fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=5`)
.then(response => {
console.log('Search response received:', response.status);
return response.json();
})
.then(data => {
console.log('Search results:', data);
if (data && data.length > 0) {
displaySearchSuggestions(data);
} else {
displaySearchSuggestions([]);
}
})
.catch(error => {
console.error('Search error:', error);
displaySearchSuggestions([]);
});
}
function displaySearchSuggestions(results) {
console.log('Displaying suggestions:', results);
if (!suggestionsDiv) {
console.error('Suggestions div not found');
return;
}
if (results.length === 0) {
suggestionsDiv.innerHTML = '<div class="p-2 text-muted small">No locations found</div>';
suggestionsDiv.style.display = 'block';
console.log('No results found, showing message');
return;
}
let html = '';
results.forEach(result => {
const displayName = result.display_name || result.name;
html += `
<div class="suggestion-item p-2 border-bottom hover-bg-light cursor-pointer"
data-lat="${result.lat}" data-lng="${result.lon}" data-name="${displayName}">
<div class="fw-medium">${result.name}</div>
<div class="text-muted small">${displayName}</div>
</div>
`;
});
suggestionsDiv.innerHTML = html;
suggestionsDiv.style.display = 'block';
console.log('Suggestions displayed, HTML length:', html.length);
// Add click handlers to suggestions
suggestionsDiv.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('click', function() {
const lat = parseFloat(this.dataset.lat);
const lng = parseFloat(this.dataset.lng);
const name = this.dataset.name;
console.log('Suggestion clicked:', {
lat,
lng,
name
});
// Update map view
map.setView([lat, lng], 15);
// Update or create marker
if (marker) {
marker.setLatLng([lat, lng]);
} else {
marker = L.marker([lat, lng], {
draggable: true
}).addTo(map);
// Add drag event listener
marker.on('dragend', function(e) {
const position = e.target.getLatLng();
latInput.value = position.lat.toFixed(6);
lngInput.value = position.lng.toFixed(6);
});
}
// Update input fields
latInput.value = lat.toFixed(6);
lngInput.value = lng.toFixed(6);
searchInput.value = name;
// Hide suggestions
suggestionsDiv.style.display = 'none';
});
});
}
function hideSuggestions() {
if (suggestionsDiv) {
suggestionsDiv.style.display = 'none';
}
}
// Event listeners
if (searchBtn && searchInput) {
console.log('Search elements found:', {
searchBtn,
searchInput
});
searchBtn.addEventListener('click', function() {
console.log('Search button clicked, value:', searchInput.value);
searchLocation(searchInput.value);
});
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
console.log('Enter key pressed, value:', searchInput.value);
e.preventDefault();
hideSuggestions();
searchLocation(searchInput.value);
}
});
// Add debounced search for real-time suggestions
searchInput.addEventListener('input', function(e) {
const query = e.target.value.trim();
console.log('Input event, query:', query);
// Clear previous timeout
if (searchTimeout) {
clearTimeout(searchTimeout);
}
if (query.length < 2) {
hideSuggestions();
return;
}
// Debounce search (wait 300ms after user stops typing)
searchTimeout = setTimeout(() => {
console.log('Debounced search triggered for:', query);
searchLocation(query);
}, 300);
});
// Hide suggestions when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.address-map-component')) {
hideSuggestions();
}
});
// Hide suggestions on ESC key
searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
hideSuggestions();
}
});
} else {
console.error('Search elements not found:', {
searchBtn,
searchInput
});
}
// Update marker when coordinates are manually entered
[latInput, lngInput].forEach(input => {
input.addEventListener('change', function() {
const lat = parseFloat(latInput.value);
const lng = parseFloat(lngInput.value);
if (!isNaN(lat) && !isNaN(lng) && map && marker) {
marker.setLatLng([lat, lng]);
map.setView([lat, lng], 15);
}
});
});
// Initialize map when component is ready
setTimeout(() => {
initializeMap();
}, 100);
}
// Make functions globally available for dynamic initialization
window.initializeMapFromPlaceholder = initializeMapFromPlaceholder;
window.initializeAddressMap = initializeAddressMap;
});
</script>