542 lines
20 KiB
PHP
542 lines
20 KiB
PHP
@props(['club' => null, 'mode' => 'create'])
|
|
|
|
@php
|
|
$isEdit = $mode === 'edit' && $club;
|
|
@endphp
|
|
|
|
<div class="container-fluid px-0">
|
|
<h5 class="fw-bold mb-3">Location</h5>
|
|
<p class="text-muted mb-4">Set your club's geographic location and regional settings</p>
|
|
|
|
<!-- Country, Timezone, Currency Row -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<x-nationality-dropdown
|
|
name="country"
|
|
id="country"
|
|
label="Country"
|
|
:value="$club->country ?? old('country', 'Bahrain')"
|
|
:required="true"
|
|
:error="null" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<x-timezone-dropdown-bootstrap
|
|
name="timezone"
|
|
id="timezone"
|
|
:value="$club->timezone ?? old('timezone', 'Asia/Bahrain')"
|
|
:required="false"
|
|
:error="null" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<x-currency-dropdown-bootstrap
|
|
name="currency"
|
|
id="currency"
|
|
:value="$club->currency ?? old('currency', 'BHD')"
|
|
:required="false"
|
|
:error="null" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address -->
|
|
<div class="mb-4">
|
|
<label for="address" class="form-label">Street Address</label>
|
|
<textarea class="form-control"
|
|
id="address"
|
|
name="address"
|
|
rows="2"
|
|
placeholder="Enter the full street address of your club">{{ $club->address ?? old('address') }}</textarea>
|
|
<small class="text-muted">Full address including building number, street name, area, etc.</small>
|
|
</div>
|
|
|
|
<!-- Map -->
|
|
<div class="mb-4">
|
|
<label class="form-label">Location on Map</label>
|
|
<div id="modalClubMap" style="height: 400px; border-radius: 0.5rem; border: 1px solid hsl(var(--border));"></div>
|
|
<small class="text-muted">Drag the marker to set the exact location of your club</small>
|
|
</div>
|
|
|
|
<!-- GPS Coordinates -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<label for="gps_lat" class="form-label">
|
|
<i class="bi bi-geo-alt me-1"></i>Latitude
|
|
</label>
|
|
<input type="number"
|
|
class="form-control"
|
|
id="gps_lat"
|
|
name="gps_lat"
|
|
value="{{ $club->gps_lat ?? old('gps_lat') }}"
|
|
step="0.0000001"
|
|
min="-90"
|
|
max="90"
|
|
placeholder="e.g., 26.0667">
|
|
<small class="text-muted">Decimal degrees (-90 to 90)</small>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="gps_long" class="form-label">
|
|
<i class="bi bi-geo-alt me-1"></i>Longitude
|
|
</label>
|
|
<input type="number"
|
|
class="form-control"
|
|
id="gps_long"
|
|
name="gps_long"
|
|
value="{{ $club->gps_long ?? old('gps_long') }}"
|
|
step="0.0000001"
|
|
min="-180"
|
|
max="180"
|
|
placeholder="e.g., 50.5577">
|
|
<small class="text-muted">Decimal degrees (-180 to 180)</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Google Maps Link -->
|
|
<div class="mb-4">
|
|
<label for="google_maps_link" class="form-label">
|
|
<i class="bi bi-google me-1"></i>Google Maps Link (Optional)
|
|
</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white">
|
|
<i class="bi bi-link-45deg"></i>
|
|
</span>
|
|
<input type="url"
|
|
class="form-control"
|
|
id="google_maps_link"
|
|
name="google_maps_link"
|
|
placeholder="Paste Google Maps share link here..."
|
|
pattern="https?://.*google\.com/maps.*|https?://goo\.gl/maps/.*">
|
|
</div>
|
|
<small class="text-muted">Paste a Google Maps share URL to auto-fill coordinates</small>
|
|
</div>
|
|
|
|
<!-- Quick Location Actions -->
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<button type="button" class="btn btn-outline-primary btn-sm" onclick="getCurrentLocation()">
|
|
<i class="bi bi-crosshair me-2"></i>Use My Current Location
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="centerOnCountry()">
|
|
<i class="bi bi-globe me-2"></i>Center on Selected Country
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script>
|
|
let clubMap = null;
|
|
let clubMarker = null;
|
|
let countriesData = null;
|
|
let mapInitialized = false;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Load countries data
|
|
fetch('/data/countries.json')
|
|
.then(response => response.json())
|
|
.then(countries => {
|
|
countriesData = countries;
|
|
|
|
// PART 1A: Device-based preselection on modal open (create mode only)
|
|
const clubForm = document.getElementById('clubForm');
|
|
if (clubForm && clubForm.dataset.mode === 'create') {
|
|
detectAndPreselectCountries(countries);
|
|
}
|
|
|
|
setupLocationHandlers();
|
|
})
|
|
.catch(error => console.error('Error loading countries:', error));
|
|
|
|
// Initialize map when location tab is shown OR when modal opens with location tab active
|
|
const locationTab = document.getElementById('location-tab');
|
|
const clubModal = document.getElementById('clubModal');
|
|
|
|
// Function to initialize map if not already done
|
|
function tryInitializeMap() {
|
|
if (!mapInitialized && locationTab && locationTab.classList.contains('active')) {
|
|
setTimeout(() => {
|
|
initializeMap();
|
|
mapInitialized = true;
|
|
}, 100);
|
|
} else if (clubMap) {
|
|
// ISSUE 4 FIX: Fix gray tiles by invalidating size
|
|
setTimeout(() => {
|
|
clubMap.invalidateSize();
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
// Listen for tab shown event
|
|
if (locationTab) {
|
|
locationTab.addEventListener('shown.bs.tab', tryInitializeMap);
|
|
}
|
|
|
|
// Listen for modal shown event (for edit mode when location tab is already active)
|
|
if (clubModal) {
|
|
clubModal.addEventListener('shown.bs.modal', function() {
|
|
// Reset map initialization flag when modal opens
|
|
mapInitialized = false;
|
|
// Try to initialize map after modal is fully shown
|
|
setTimeout(tryInitializeMap, 150);
|
|
});
|
|
|
|
// Clean up map when modal closes
|
|
clubModal.addEventListener('hidden.bs.modal', function() {
|
|
if (clubMap) {
|
|
clubMap.remove();
|
|
clubMap = null;
|
|
clubMarker = null;
|
|
mapInitialized = false;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// PART 1A: Detect device location and preselect country/timezone/currency
|
|
function detectAndPreselectCountries(countries) {
|
|
if (!navigator.geolocation) {
|
|
// Fallback to default (Bahrain)
|
|
preselectCountryData('Bahrain', countries);
|
|
return;
|
|
}
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
function(position) {
|
|
const lat = position.coords.latitude;
|
|
const lon = position.coords.longitude;
|
|
|
|
// Use reverse geocoding to get country
|
|
fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=en`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const countryName = data.countryName || 'Bahrain';
|
|
preselectCountryData(countryName, countries);
|
|
})
|
|
.catch(() => {
|
|
// Fallback to default
|
|
preselectCountryData('Bahrain', countries);
|
|
});
|
|
},
|
|
function() {
|
|
// Geolocation denied or failed, use default
|
|
preselectCountryData('Bahrain', countries);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Preselect country, timezone, and currency based on detected/default country
|
|
function preselectCountryData(countryName, countries) {
|
|
const country = countries.find(c => c.name.toLowerCase() === countryName.toLowerCase());
|
|
if (!country) return;
|
|
|
|
// Preselect country
|
|
const countryInput = document.getElementById('country');
|
|
if (countryInput && !countryInput.value) {
|
|
countryInput.value = country.iso3 || country.name;
|
|
countryInput.dispatchEvent(new Event('change'));
|
|
}
|
|
|
|
// Preselect timezone using Bootstrap dropdown
|
|
const timezoneInput = document.getElementById('timezone');
|
|
if (timezoneInput && country.timezone && !timezoneInput.value && typeof setTimezoneValue === 'function') {
|
|
setTimezoneValue('timezone', country.timezone, countries);
|
|
}
|
|
|
|
// Preselect currency using Bootstrap dropdown
|
|
const currencyInput = document.getElementById('currency');
|
|
if (currencyInput && country.currency && !currencyInput.value && typeof setCurrencyValue === 'function') {
|
|
setCurrencyValue('currency', country.currency, countries);
|
|
}
|
|
|
|
// Set map center to country
|
|
if (country.latitude && country.longitude) {
|
|
const lat = parseFloat(country.latitude);
|
|
const lng = parseFloat(country.longitude);
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
const latInput = document.getElementById('gps_lat');
|
|
const lngInput = document.getElementById('gps_long');
|
|
if (latInput && !latInput.value) latInput.value = lat.toFixed(7);
|
|
if (lngInput && !lngInput.value) lngInput.value = lng.toFixed(7);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize Leaflet map
|
|
function initializeMap() {
|
|
const mapElement = document.getElementById('modalClubMap');
|
|
if (!mapElement) return;
|
|
|
|
const lat = parseFloat(document.getElementById('gps_lat')?.value) || 26.0667;
|
|
const lng = parseFloat(document.getElementById('gps_long')?.value) || 50.5577;
|
|
|
|
// ISSUE 4 FIX: Initialize map without attribution control
|
|
clubMap = L.map('clubMap', {
|
|
attributionControl: false // Disable attribution
|
|
}).setView([lat, lng], 13);
|
|
|
|
// ISSUE 4 FIX: Add tile layer without attribution text
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '', // Empty attribution
|
|
maxZoom: 19
|
|
}).addTo(clubMap);
|
|
|
|
// Add draggable marker
|
|
clubMarker = L.marker([lat, lng], {
|
|
draggable: true
|
|
}).addTo(clubMap);
|
|
|
|
// Update coordinates when marker is dragged
|
|
clubMarker.on('dragend', function(e) {
|
|
const position = e.target.getLatLng();
|
|
updateCoordinates(position.lat, position.lng);
|
|
});
|
|
|
|
// Update marker when coordinates are manually entered
|
|
const latInput = document.getElementById('gps_lat');
|
|
const lngInput = document.getElementById('gps_long');
|
|
|
|
if (latInput && lngInput) {
|
|
latInput.addEventListener('change', function() {
|
|
updateMarkerPosition();
|
|
});
|
|
lngInput.addEventListener('change', function() {
|
|
updateMarkerPosition();
|
|
});
|
|
}
|
|
|
|
// ISSUE 4 FIX: Fix initial rendering after a short delay
|
|
setTimeout(() => {
|
|
if (clubMap) {
|
|
clubMap.invalidateSize();
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// Update coordinates in inputs
|
|
function updateCoordinates(lat, lng) {
|
|
const latInput = document.getElementById('gps_lat');
|
|
const lngInput = document.getElementById('gps_long');
|
|
|
|
if (latInput) latInput.value = lat.toFixed(7);
|
|
if (lngInput) lngInput.value = lng.toFixed(7);
|
|
}
|
|
|
|
// Update marker position from inputs
|
|
function updateMarkerPosition() {
|
|
const lat = parseFloat(document.getElementById('gps_lat')?.value);
|
|
const lng = parseFloat(document.getElementById('gps_long')?.value);
|
|
|
|
if (!isNaN(lat) && !isNaN(lng) && clubMarker && clubMap) {
|
|
const newPos = L.latLng(lat, lng);
|
|
clubMarker.setLatLng(newPos);
|
|
clubMap.setView(newPos, clubMap.getZoom());
|
|
}
|
|
}
|
|
|
|
// Setup location-related event handlers
|
|
function setupLocationHandlers() {
|
|
// Watch for country changes
|
|
const countryInput = document.getElementById('country');
|
|
if (countryInput) {
|
|
// Use MutationObserver to detect value changes
|
|
const observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
|
|
handleCountryChange(countryInput.value);
|
|
}
|
|
});
|
|
});
|
|
observer.observe(countryInput, { attributes: true });
|
|
|
|
// Also listen for direct changes
|
|
countryInput.addEventListener('change', function() {
|
|
handleCountryChange(this.value);
|
|
});
|
|
|
|
// Check periodically for value changes (fallback)
|
|
let lastCountryValue = countryInput.value;
|
|
setInterval(function() {
|
|
if (countryInput.value !== lastCountryValue) {
|
|
lastCountryValue = countryInput.value;
|
|
handleCountryChange(countryInput.value);
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
// Parse Google Maps link
|
|
const googleMapsInput = document.getElementById('google_maps_link');
|
|
if (googleMapsInput) {
|
|
googleMapsInput.addEventListener('change', function() {
|
|
parseGoogleMapsLink(this.value);
|
|
});
|
|
}
|
|
}
|
|
|
|
// PART 1B: Handle country change - update timezone, currency, and map
|
|
function handleCountryChange(countryName) {
|
|
if (!countriesData || !countryName) return;
|
|
|
|
const country = countriesData.find(c =>
|
|
c.name.toLowerCase() === countryName.toLowerCase() ||
|
|
c.iso3 === countryName
|
|
);
|
|
|
|
if (!country) return;
|
|
|
|
// PART 1B: Always update timezone to match country using Bootstrap dropdown
|
|
if (country.timezone && typeof setTimezoneValue === 'function') {
|
|
setTimezoneValue('timezone', country.timezone, countriesData);
|
|
}
|
|
|
|
// PART 1B: Always update currency to match country using Bootstrap dropdown
|
|
if (country.currency && typeof setCurrencyValue === 'function') {
|
|
setCurrencyValue('currency', country.currency, countriesData);
|
|
}
|
|
|
|
// Center map on country
|
|
if (country.latitude && country.longitude) {
|
|
const lat = parseFloat(country.latitude);
|
|
const lng = parseFloat(country.longitude);
|
|
|
|
if (!isNaN(lat) && !isNaN(lng)) {
|
|
// Update coordinates
|
|
const latInput = document.getElementById('gps_lat');
|
|
const lngInput = document.getElementById('gps_long');
|
|
|
|
// Only update if empty or user wants to recenter
|
|
const shouldUpdate = !latInput?.value || !lngInput?.value;
|
|
|
|
if (shouldUpdate) {
|
|
updateCoordinates(lat, lng);
|
|
}
|
|
|
|
// Always recenter map view on country
|
|
if (clubMarker && clubMap) {
|
|
if (shouldUpdate) {
|
|
clubMarker.setLatLng([lat, lng]);
|
|
}
|
|
clubMap.setView([lat, lng], 10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get current location using browser geolocation
|
|
function getCurrentLocation() {
|
|
if (!navigator.geolocation) {
|
|
alert('Geolocation is not supported by your browser');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const btn = event.target.closest('button');
|
|
const originalHtml = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Getting location...';
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
function(position) {
|
|
const lat = position.coords.latitude;
|
|
const lng = position.coords.longitude;
|
|
|
|
updateCoordinates(lat, lng);
|
|
if (clubMarker && clubMap) {
|
|
clubMarker.setLatLng([lat, lng]);
|
|
clubMap.setView([lat, lng], 15);
|
|
}
|
|
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalHtml;
|
|
|
|
if (typeof Toast !== 'undefined') {
|
|
Toast.success('Success', 'Location updated successfully');
|
|
}
|
|
},
|
|
function(error) {
|
|
console.error('Geolocation error:', error);
|
|
alert('Unable to get your location. Please check your browser permissions.');
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalHtml;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Center map on selected country
|
|
function centerOnCountry() {
|
|
const countryInput = document.getElementById('country');
|
|
if (!countryInput || !countriesData) return;
|
|
|
|
const countryName = countryInput.value;
|
|
const country = countriesData.find(c =>
|
|
c.name.toLowerCase() === countryName.toLowerCase() ||
|
|
c.iso3 === countryName
|
|
);
|
|
|
|
if (country && country.latitude && country.longitude) {
|
|
const lat = parseFloat(country.latitude);
|
|
const lng = parseFloat(country.longitude);
|
|
|
|
if (!isNaN(lat) && !isNaN(lng) && clubMap) {
|
|
clubMap.setView([lat, lng], 10);
|
|
|
|
if (typeof Toast !== 'undefined') {
|
|
Toast.info('Info', `Centered on ${country.name}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse Google Maps link to extract coordinates
|
|
function parseGoogleMapsLink(url) {
|
|
if (!url) return;
|
|
|
|
try {
|
|
// Try to extract coordinates from various Google Maps URL formats
|
|
let lat, lng;
|
|
|
|
// Format: @lat,lng
|
|
const atMatch = url.match(/@(-?\d+\.\d+),(-?\d+\.\d+)/);
|
|
if (atMatch) {
|
|
lat = parseFloat(atMatch[1]);
|
|
lng = parseFloat(atMatch[2]);
|
|
}
|
|
|
|
// Format: !3d and !4d
|
|
if (!lat || !lng) {
|
|
const dMatch = url.match(/!3d(-?\d+\.\d+)!4d(-?\d+\.\d+)/);
|
|
if (dMatch) {
|
|
lat = parseFloat(dMatch[1]);
|
|
lng = parseFloat(dMatch[2]);
|
|
}
|
|
}
|
|
|
|
// Format: ll=lat,lng
|
|
if (!lat || !lng) {
|
|
const llMatch = url.match(/ll=(-?\d+\.\d+),(-?\d+\.\d+)/);
|
|
if (llMatch) {
|
|
lat = parseFloat(llMatch[1]);
|
|
lng = parseFloat(llMatch[2]);
|
|
}
|
|
}
|
|
|
|
if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
|
|
updateCoordinates(lat, lng);
|
|
if (clubMarker && clubMap) {
|
|
clubMarker.setLatLng([lat, lng]);
|
|
clubMap.setView([lat, lng], 15);
|
|
}
|
|
|
|
if (typeof Toast !== 'undefined') {
|
|
Toast.success('Success', 'Coordinates extracted from Google Maps link');
|
|
}
|
|
} else {
|
|
if (typeof Toast !== 'undefined') {
|
|
Toast.warning('Warning', 'Could not extract coordinates from the link');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing Google Maps link:', error);
|
|
if (typeof Toast !== 'undefined') {
|
|
Toast.error('Error', 'Invalid Google Maps link');
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
@endpush
|