mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 06:11:09 -05:00
Implement LocationDisplayComponent and LocationMapComponent for interactive map features; add event handling and state management
This commit is contained in:
190
resources/views/livewire/location/location-display.blade.php
Normal file
190
resources/views/livewire/location/location-display.blade.php
Normal file
@@ -0,0 +1,190 @@
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Ensure map container and its elements stay below other UI elements */
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
.leaflet-control {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
/* Custom marker cluster styling */
|
||||
.marker-cluster {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
border: 2px solid rgba(0, 120, 255, 0.5);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.marker-cluster div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
background-color: rgba(0, 120, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Custom info window styling */
|
||||
.location-info-window {
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.location-info-window h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
<div class="location-display-component">
|
||||
<div wire:ignore class="relative mb-4" style="z-index: 1;">
|
||||
<div id="locationMap" class="h-[400px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
||||
|
||||
@if($showInfoWindow && $activeMarker)
|
||||
<div class="location-info-window absolute top-4 right-4 z-10">
|
||||
<div class="flex justify-between items-start">
|
||||
<h3 class="text-lg font-semibold">{{ $activeMarker['name'] ?? 'Location' }}</h3>
|
||||
<button wire:click="closeInfoWindow" class="text-gray-500 hover:text-gray-700">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
@if(isset($activeMarker['address']))
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ $activeMarker['address']['street'] ?? '' }}
|
||||
{{ $activeMarker['address']['city'] ?? '' }}
|
||||
{{ $activeMarker['address']['state'] ?? '' }}
|
||||
{{ $activeMarker['address']['country'] ?? '' }}
|
||||
</p>
|
||||
@endif
|
||||
@if(isset($activeMarker['description']))
|
||||
<p class="mt-2 text-sm">{{ $activeMarker['description'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('livewire:initialized', function () {
|
||||
const map = L.map('locationMap');
|
||||
let markerClusterGroup;
|
||||
let markers = {};
|
||||
|
||||
// Initialize map with OSM tiles
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
// Initialize marker cluster group
|
||||
markerClusterGroup = L.markerClusterGroup({
|
||||
maxClusterRadius: @entangle('clusterRadius'),
|
||||
spiderfyOnMaxZoom: true,
|
||||
showCoverageOnHover: false,
|
||||
zoomToBoundsOnClick: true,
|
||||
removeOutsideVisibleBounds: true,
|
||||
iconCreateFunction: function(cluster) {
|
||||
const count = cluster.getChildCount();
|
||||
return L.divIcon({
|
||||
html: `<div><span>${count}</span></div>`,
|
||||
className: 'marker-cluster',
|
||||
iconSize: L.point(40, 40)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer(markerClusterGroup);
|
||||
|
||||
// Listen for marker updates from Livewire
|
||||
@this.on('updateMarkers', (markerData) => {
|
||||
// Clear existing markers
|
||||
markerClusterGroup.clearLayers();
|
||||
markers = {};
|
||||
|
||||
// Add new markers
|
||||
markerData.forEach(marker => {
|
||||
const leafletMarker = L.marker([marker.lat, marker.lng], {
|
||||
icon: getMarkerIcon(marker.category)
|
||||
});
|
||||
|
||||
leafletMarker.on('click', () => {
|
||||
@this.markerClicked(marker.id);
|
||||
});
|
||||
|
||||
markers[marker.id] = leafletMarker;
|
||||
markerClusterGroup.addLayer(leafletMarker);
|
||||
});
|
||||
|
||||
// Fit bounds if available
|
||||
if (@this.bounds) {
|
||||
map.fitBounds([
|
||||
[@this.bounds.south, @this.bounds.west],
|
||||
[@this.bounds.north, @this.bounds.east]
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle cluster clicks
|
||||
markerClusterGroup.on('clusterclick', (e) => {
|
||||
const clusterMarkers = e.layer.getAllChildMarkers().map(marker => {
|
||||
const markerId = Object.keys(markers).find(key => markers[key] === marker);
|
||||
return { id: markerId, lat: marker.getLatLng().lat, lng: marker.getLatLng().lng };
|
||||
});
|
||||
@this.clusterClicked(clusterMarkers);
|
||||
});
|
||||
|
||||
// Handle map bounds changes
|
||||
map.on('moveend', () => {
|
||||
const bounds = map.getBounds();
|
||||
@this.boundsChanged({
|
||||
north: bounds.getNorth(),
|
||||
south: bounds.getSouth(),
|
||||
east: bounds.getEast(),
|
||||
west: bounds.getWest()
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize markers
|
||||
@this.getMarkers().then(markerData => {
|
||||
@this.emit('updateMarkers', markerData);
|
||||
});
|
||||
|
||||
// Helper function to get marker icon based on category
|
||||
function getMarkerIcon(category) {
|
||||
// Customize icons based on category
|
||||
return L.divIcon({
|
||||
className: `marker-icon marker-${category || 'default'}`,
|
||||
html: `<div class="w-8 h-8 rounded-full bg-blue-500 border-2 border-white shadow-lg flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32]
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user