Example SVG Image يرجى الانتظار أثناء تحميل الخريطة
// SUPER SIMPLE photo upload let photoUploadSetup = false; let photoClickHandler = null; let photoChangeHandler = null; function initializePhotoUpload() { // Only set up once if (photoUploadSetup) { console.log('Photo upload already initialized, skipping'); return; } console.log('=========================================='); console.log('INITIALIZING PHOTO UPLOAD'); console.log('=========================================='); const uploadZone = document.getElementById('addStoryPhotoUpload'); const fileInput = document.getElementById('addStoryPhotoInput'); console.log('Upload zone element:', uploadZone); console.log('File input element:', fileInput); if (!uploadZone || !fileInput) { console.error('ERROR: Elements not found!'); console.error('uploadZone:', uploadZone); console.error('fileInput:', fileInput); return; } // Remove any existing handlers first if (photoClickHandler) { uploadZone.removeEventListener('click', photoClickHandler); } if (photoChangeHandler) { fileInput.removeEventListener('change', photoChangeHandler); } console.log('Adding click handler to upload zone...'); // Click handler - prevent double-trigger photoClickHandler = function(e) { console.log('**** UPLOAD ZONE CLICKED ****'); e.preventDefault(); e.stopPropagation(); console.log('Clicking file input...'); fileInput.click(); }; uploadZone.addEventListener('click', photoClickHandler, { once: false }); // Change handler photoChangeHandler = function(e) { console.log('**** FILE INPUT CHANGED ****'); e.preventDefault(); e.stopPropagation(); const file = e.target.files[0]; console.log('File:', file); if (!file) { console.log('No file selected'); return; } console.log('File name:', file.name); console.log('File size:', file.size); console.log('File type:', file.type); // Read file const reader = new FileReader(); reader.onload = function(event) { console.log('File read complete!'); const imgData = event.target.result; // Update upload area preview const preview = document.getElementById('addStoryPhotoPreview'); if (preview) { preview.innerHTML = ''; console.log('Upload area preview updated'); } else { console.error('Preview div not found!'); } // Update preview card const previewImg = document.getElementById('addStoryPreviewImage'); const previewContainer = document.getElementById('addStoryPreviewImageContainer'); if (previewImg) { previewImg.src = imgData; console.log('Preview card image updated'); } else { console.error('Preview image not found!'); } if (previewContainer) { previewContainer.style.display = 'block'; console.log('Preview container shown'); } else { console.error('Preview container not found!'); } console.log('ALL PREVIEWS UPDATED!'); }; reader.onerror = function(error) { console.error('FileReader error:', error); }; console.log('Reading file as data URL...'); reader.readAsDataURL(file); }; fileInput.addEventListener('change', photoChangeHandler, { once: false }); photoUploadSetup = true; console.log('Photo upload setup complete!'); console.log('=========================================='); } // Initialize Bootstrap tabs for Add Story modal function initializeAddStoryTabs() { // Use Bootstrap's tab shown event instead of manual click handling const tabButtons = document.querySelectorAll('#addStoryTabs button[data-bs-toggle="tab"]'); tabButtons.forEach(button => { button.addEventListener('shown.bs.tab', function(event) { const target = event.target.getAttribute('data-bs-target'); // If Location tab is activated, ensure map is initialized and sized correctly if (target === '#content-location') { console.log('Location tab activated - checking map status'); // Increase timeout to ensure tab is fully visible (Bootstrap fade transition is ~150ms) setTimeout(function() { // Check if map exists and is a valid Leaflet map const isValidMap = window.addStoryMap && typeof window.addStoryMap.setView === 'function' && typeof window.addStoryMap.invalidateSize === 'function'; if (isValidMap) { console.log('Map exists - refreshing size'); window.addStoryMap.invalidateSize(); } else { console.log('Map not initialized - initializing now'); initializeAddStoryMap(); } }, 300); } }); }); } // Initialize the map for Add Story modal - MOVED TO GLOBAL SCOPE function initializeAddStoryMap() { const mapDiv = document.getElementById('addStoryMap'); if (!mapDiv) { console.warn('addStoryMap div not found'); return; } // Check if the tab pane is visible const tabPane = document.getElementById('content-location'); if (!tabPane) { console.warn('content-location tab pane not found'); return; } const tabPaneVisible = tabPane.classList.contains('show') && tabPane.classList.contains('active'); if (!tabPaneVisible) { console.log('Location tab pane is not fully visible yet'); return; } // Check if element is actually visible (not display:none) const isVisible = mapDiv.offsetParent !== null; if (!isVisible) { console.warn('addStoryMap div exists but is not visible (display:none)'); return; } // Check if Leaflet is loaded if (typeof L === 'undefined') { console.error('Leaflet library (L) is not loaded!'); return; } // CRITICAL: Check if the div already has a Leaflet map attached if (mapDiv._leaflet_id) { console.log('Removing old Leaflet instance from div'); delete mapDiv._leaflet_id; } // Check if map is already a valid Leaflet map const isValidMap = window.addStoryMap && typeof window.addStoryMap.setView === 'function' && typeof window.addStoryMap.getCenter === 'function'; if (!isValidMap) { console.log('Initializing Add Story map'); // Remove _leaflet_id if present if (mapDiv._leaflet_id) { delete mapDiv._leaflet_id; } // DON'T call .remove() on the map - it removes the container! // Just clear the reference and any child elements if (window.addStoryMap) { console.log('Clearing old map reference (not calling .remove())'); window.addStoryMap = null; } // Clear any leftover Leaflet child elements manually const children = mapDiv.querySelectorAll('[class*="leaflet"]'); if (children.length > 0) { console.log('Removing', children.length, 'Leaflet child elements'); children.forEach(child => child.remove()); } // Clear the div's innerHTML to remove any leftover content mapDiv.innerHTML = ''; // Ensure _leaflet_id is gone after cleanup if (mapDiv._leaflet_id) { delete mapDiv._leaflet_id; } try { // Verify div is still in DOM const mapDivCheck = document.getElementById('addStoryMap'); if (!mapDivCheck) { console.error('Map div disappeared from DOM!'); return; } console.log('Map div confirmed in DOM, creating map...'); // Create map centered on Gaza window.addStoryMap = L.map('addStoryMap').setView([31.5, 34.466667], 12); // Add OpenStreetMap tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }).addTo(window.addStoryMap); // Add click handler to map window.addStoryMap.on('click', function(e) { const lat = e.latlng.lat; const lng = e.latlng.lng; console.log('Map clicked at:', lat, lng); // Update coordinates document.getElementById('addStoryLatitude').value = lat.toFixed(7); document.getElementById('addStoryLongitude').value = lng.toFixed(7); // Load nearby locations loadNearbyLocationsForAddStory(lat, lng); // Add or move marker (using circle marker - doubled size) if (window.addStoryMarker) { window.addStoryMarker.setLatLng([lat, lng]); } else { window.addStoryMarker = L.circleMarker([lat, lng], { radius: 16, fillColor: '#1967d2', color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.8 }).addTo(window.addStoryMap); } }); console.log('Add Story map initialized successfully'); } catch (error) { console.error('Error initializing map:', error); window.addStoryMap = null; } } else { // Map already exists and is valid, just invalidate size in case of layout changes console.log('Map already initialized, refreshing size'); setTimeout(function() { try { window.addStoryMap.invalidateSize(); } catch (e) { console.error('Error invalidating map size:', e); } }, 100); } } // Initialize cascading location dropdowns for Add Story modal function initializeLocationDropdowns() { const locationTypeSelect = document.getElementById('addStoryLocationType'); const countrySelect = document.getElementById('addStoryCountry'); const regionSelect = document.getElementById('addStoryRegion'); const citySelect = document.getElementById('addStoryCity'); const districtSelect = document.getElementById('addStoryDistrict'); const neighborhoodSelect = document.getElementById('addStoryNeighborhood'); // Helper function to populate dropdown async function populateDropdown(selectElement, apiParam, filters = {}) { if (!selectElement) { console.warn(`populateDropdown: selectElement is null for ${apiParam}`); return; } // Build query string let queryParams = new URLSearchParams({ list: apiParam }); Object.keys(filters).forEach(key => { if (filters[key]) { queryParams.append(key, filters[key]); } }); const url = `api/locations/read.php?${queryParams.toString()}`; console.log(`Loading ${apiParam} from:`, url); try { const response = await fetch(url); const data = await response.json(); console.log(`API Response for ${apiParam}:`, data); // Clear existing options except the first one const firstOption = selectElement.options[0].outerHTML; selectElement.innerHTML = firstOption; // Populate with new options if (data.success && data.records && data.records.length > 0) { console.log(`Adding ${data.records.length} options to ${apiParam} dropdown`); data.records.forEach(item => { const option = document.createElement('option'); option.value = item; option.textContent = item; selectElement.appendChild(option); }); selectElement.disabled = false; } else { console.warn(`No records found for ${apiParam}. Response:`, data); selectElement.disabled = true; } } catch (error) { console.error(`Error loading ${apiParam}:`, error); selectElement.disabled = true; } } // Helper function to reset dropdown and its children function resetDropdown(selectElement, ...childrenSelects) { if (selectElement) { selectElement.selectedIndex = 0; selectElement.disabled = true; // Keep only the first option selectElement.innerHTML = selectElement.options[0].outerHTML; } childrenSelects.forEach(child => { if (child) { child.selectedIndex = 0; child.disabled = true; child.innerHTML = child.options[0].outerHTML; } }); } // Load location types, countries, and ALL locations on modal open const addStoryModal = document.getElementById('addStoryModal'); if (addStoryModal) { addStoryModal.addEventListener('shown.bs.modal', function() { console.log('Add Story Modal opened - loading initial data'); populateDropdown(locationTypeSelect, 'location_type'); populateDropdown(countrySelect, 'country'); loadAllLocations(); loadGroupsTable(); // Load groups table too // initializePhotoUpload(); // DISABLED: app.js already handles photo upload console.log('All initial data loaded'); }); } else { console.error('addStoryModal element not found!'); } // Load total count only (not all locations - too much data!) async function loadAllLocations() { const locationSearchInput = document.getElementById('addStoryLocationSearch'); try { // Get total count from database const countUrl = 'api/locations/read.php?limit=1'; console.log('⏳ Getting total location count...'); const response = await fetch(countUrl); const data = await response.json(); if (data.success && data.total) { const total = data.total; console.log(`✅ Database has ${total.toLocaleString()} total locations`); // Update placeholder - no locations loaded yet, will load on demand if (locationSearchInput) { locationSearchInput.placeholder = `${total.toLocaleString()} locations in database - use filters or type to search...`; locationSearchInput.disabled = false; } // Initialize empty - will load based on filters window.addStoryFilteredLocations = []; window.addStoryCurrentFilteredLocations = []; window.totalLocationCount = total; } else { if (locationSearchInput) { locationSearchInput.placeholder = 'Type to search locations...'; locationSearchInput.disabled = false; } window.addStoryFilteredLocations = []; window.addStoryCurrentFilteredLocations = []; } } catch (error) { console.error('❌ Error getting location count:', error); if (locationSearchInput) { locationSearchInput.placeholder = 'Type to search locations...'; locationSearchInput.disabled = false; } window.addStoryFilteredLocations = []; window.addStoryCurrentFilteredLocations = []; } } // Country change -> Load regions if (countrySelect) { countrySelect.addEventListener('change', function() { const country = this.value; if (country && country.trim() !== '') { console.log('✓ Country filter applied:', country); populateDropdown(regionSelect, 'region', { country: country }); resetDropdown(citySelect, districtSelect, neighborhoodSelect); } else { console.log('✗ Country filter removed (first option selected)'); resetDropdown(regionSelect, citySelect, districtSelect, neighborhoodSelect); } }); } else { console.warn('countrySelect not found!'); } // Region change -> Load cities if (regionSelect) { regionSelect.addEventListener('change', function() { const country = countrySelect?.value; const region = this.value; if (country && region && region.trim() !== '') { console.log('✓ Region filter applied:', region); populateDropdown(citySelect, 'city', { country: country, region: region }); resetDropdown(districtSelect, neighborhoodSelect); } else { console.log('✗ Region filter removed (first option selected)'); resetDropdown(citySelect, districtSelect, neighborhoodSelect); } }); } // City change -> Load districts if (citySelect) { citySelect.addEventListener('change', function() { const country = countrySelect?.value; const region = regionSelect?.value; const city = this.value; if (country && region && city && city.trim() !== '') { console.log('✓ City filter applied:', city); populateDropdown(districtSelect, 'district', { country: country, region: region, city: city }); resetDropdown(neighborhoodSelect); } else { console.log('✗ City filter removed (first option selected)'); resetDropdown(districtSelect, neighborhoodSelect); } }); } // District change -> Load neighborhoods if (districtSelect) { districtSelect.addEventListener('change', function() { const country = countrySelect?.value; const region = regionSelect?.value; const city = citySelect?.value; const district = this.value; if (country && region && city && district && district.trim() !== '') { console.log('✓ District filter applied:', district); populateDropdown(neighborhoodSelect, 'neighborhood', { country: country, region: region, city: city, district: district }); } else { console.log('✗ District filter removed (first option selected)'); resetDropdown(neighborhoodSelect); } }); } // Helper function to update location search based on filters (SERVER-SIDE filtering) async function updateLocationSearch() { const locationSearchInput = document.getElementById('addStoryLocationSearch'); const locationResultsDiv = document.getElementById('addStoryLocationResults'); // Collect current filter values - ONLY add to filters if value is not empty // Empty value (first option selected) = filter is canceled/removed const filters = {}; if (locationTypeSelect?.value && locationTypeSelect.value.trim() !== '') { filters.location_type = locationTypeSelect.value; } if (countrySelect?.value && countrySelect.value.trim() !== '') { filters.country = countrySelect.value; } if (regionSelect?.value && regionSelect.value.trim() !== '') { filters.region = regionSelect.value; } if (citySelect?.value && citySelect.value.trim() !== '') { filters.city = citySelect.value; } if (districtSelect?.value && districtSelect.value.trim() !== '') { filters.district = districtSelect.value; } if (neighborhoodSelect?.value && neighborhoodSelect.value.trim() !== '') { filters.neighborhood = neighborhoodSelect.value; } const filterCount = Object.keys(filters).length; const isPointType = locationTypeSelect?.value && locationTypeSelect.value.toLowerCase() === 'point'; console.log(`🔍 Active filters (${filterCount}):`, filters); // If no filters, show message and don't load if (filterCount === 0) { console.log('✓ No filters active - waiting for user to select filters or search'); if (locationSearchInput) { const total = window.totalLocationCount || 0; locationSearchInput.placeholder = `${total.toLocaleString()} locations in database - use filters or type to search...`; } if (locationResultsDiv) { locationResultsDiv.style.display = 'none'; } window.addStoryCurrentFilteredLocations = []; return; } try { // Build query parameters for server-side filtering const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { queryParams.append(key, value); } queryParams.append('limit', '1000'); // Reasonable limit for filtered results const url = `api/locations/read.php?${queryParams.toString()}`; console.log('📡 Fetching filtered locations from server:', url); const response = await fetch(url); const data = await response.json(); if (data.success && data.records) { const filtered = data.records; const count = filtered.length; const total = data.total || count; console.log(`📊 Result: ${count} locations loaded (${total} total match filters)`); // Update placeholder if (locationSearchInput) { locationSearchInput.placeholder = `${count.toLocaleString()} location${count !== 1 ? 's' : ''} match filters - type to search...`; } // Store filtered locations window.addStoryCurrentFilteredLocations = filtered; // Auto-display filtered results if (count > 0 && locationResultsDiv) { displayFilteredLocations(filtered.slice(0, 50)); // Show first 50 } else if (count === 0 && locationResultsDiv) { locationResultsDiv.innerHTML = '
' + t('text_no_matches_filters', 'No locations match the selected filters') + '
'; locationResultsDiv.style.display = 'block'; } } else { console.warn('No locations found matching filters'); window.addStoryCurrentFilteredLocations = []; if (locationResultsDiv) { locationResultsDiv.innerHTML = '
No locations found
'; locationResultsDiv.style.display = 'block'; } } } catch (error) { console.error('❌ Error fetching filtered locations:', error); window.addStoryCurrentFilteredLocations = []; } } // Helper function to display filtered locations function displayFilteredLocations(locations) { const locationResultsDiv = document.getElementById('addStoryLocationResults'); if (!locationResultsDiv) { console.error('Location results div not found!'); return; } console.log(`Displaying ${locations.length} locations`); if (locations.length === 0) { locationResultsDiv.innerHTML = '
' + t('text_no_locations_found', 'No locations found') + '
'; locationResultsDiv.style.display = 'block'; return; } // Display results locationResultsDiv.innerHTML = locations.map(loc => { // Build hierarchical path const locationParts = []; if (loc.neighborhood) locationParts.push(loc.neighborhood); if (loc.district) locationParts.push(loc.district); if (loc.city) locationParts.push(loc.city); if (loc.region) locationParts.push(loc.region); if (loc.country) locationParts.push(loc.country); // Remove duplicates and join const uniqueParts = [...new Set(locationParts)]; const subText = uniqueParts.join(' → '); // Get location type as plain text in parentheses (capitalize first letter) const typeText = loc.location_type ? `(${loc.location_type.charAt(0).toUpperCase() + loc.location_type.slice(1)})` : ''; return `
${loc.location_name}
${typeText}${subText ? ` ${subText}` : ''}
`; }).join(''); locationResultsDiv.style.display = 'block'; console.log('Location results dropdown displayed'); // Add click handlers to results const items = locationResultsDiv.querySelectorAll('.search-result-item'); console.log(`Adding click handlers to ${items.length} items`); items.forEach((item, index) => { // Remove any existing listeners to prevent duplicates const newItem = item.cloneNode(true); item.parentNode.replaceChild(newItem, item); newItem.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); console.log(`>>> Location item ${index} CLICKED <<<`); console.log('Item data:', { id: this.getAttribute('data-location-id'), name: this.getAttribute('data-location-name'), lat: this.getAttribute('data-lat'), lng: this.getAttribute('data-lng') }); selectLocation(this); }); console.log(`Click handler added to item ${index}`); }); } // Helper function to select a location function selectLocation(element) { const locationId = element.getAttribute('data-location-id'); const locationName = element.getAttribute('data-location-name'); const lat = parseFloat(element.getAttribute('data-lat')); const lng = parseFloat(element.getAttribute('data-lng')); console.log('=== SELECT LOCATION CALLED ==='); console.log('Location ID:', locationId); console.log('Location Name:', locationName); console.log('Coordinates:', lat, lng); const locationIdInput = document.getElementById('addStoryLocationId'); const locationSearchInput = document.getElementById('addStoryLocationSearch'); const locationDisplayDiv = document.getElementById('addStoryLocationDisplay'); const locationResultsDiv = document.getElementById('addStoryLocationResults'); // Set values if (locationIdInput) { locationIdInput.value = locationId; console.log('Location ID set'); } if (locationSearchInput) { locationSearchInput.value = locationName; console.log('Search input updated'); } if (locationDisplayDiv) { locationDisplayDiv.textContent = `Currently selected: ${locationName}`; console.log('Display text updated'); } // Update sidebar location display const sidebarLocationName = document.getElementById('addStorySidebarLocationName'); if (sidebarLocationName) { sidebarLocationName.textContent = locationName; console.log('Sidebar location name updated'); // Update preview location if (typeof window.updateStoryPreviewLocation === 'function') { window.updateStoryPreviewLocation(); } } // Update coordinates const latInput = document.getElementById('addStoryLatitude'); const lngInput = document.getElementById('addStoryLongitude'); if (latInput) { latInput.value = lat; console.log('Latitude set to', lat); } if (lngInput) { lngInput.value = lng; console.log('Longitude set to', lng); } // Load nearby locations loadNearbyLocationsForAddStory(lat, lng); // Hide the dropdown when location is selected if (locationResultsDiv) { locationResultsDiv.innerHTML = ''; locationResultsDiv.style.display = 'none'; console.log('Dropdown hidden'); } // Update map if available - zoom in to the location console.log('Checking map availability...'); console.log('window.addStoryMap exists?', !!window.addStoryMap); console.log('Coordinates valid?', !isNaN(lat) && !isNaN(lng)); // Helper function to check if map is valid Leaflet map function isValidLeafletMap(map) { return map && typeof map.setView === 'function' && typeof map.getCenter === 'function'; } // Update map with selected location function updateMapWithLocation(lat, lng) { // Check if Leaflet is loaded if (typeof L === 'undefined') { console.error('Leaflet library not loaded'); return; } if (!isValidLeafletMap(window.addStoryMap)) { console.error('Map is not a valid Leaflet map object'); return; } try { console.log('*** ZOOMING MAP TO LOCATION ***'); console.log('Target coordinates:', lat, lng); // Zoom to the location with a higher zoom level for better focus window.addStoryMap.setView([lat, lng], 18, { animate: true, duration: 1 }); console.log('Map setView called successfully'); // Remove existing marker if any if (window.addStoryMarker) { window.addStoryMap.removeLayer(window.addStoryMarker); console.log('Old marker removed'); } // Add new circle marker at the location (doubled size) window.addStoryMarker = L.circleMarker([lat, lng], { radius: 16, fillColor: '#1967d2', color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.8 }).addTo(window.addStoryMap); console.log('New circle marker added at', lat, lng); } catch (error) { console.error('Error updating map:', error); } } // Check if coordinates are valid if (isNaN(lat) || isNaN(lng)) { console.warn('Invalid coordinates:', lat, lng); return; } // Check if the Add Story modal is actually open/visible const addStoryModal = document.getElementById('addStoryModal'); const isModalVisible = addStoryModal && addStoryModal.classList.contains('show'); if (!isModalVisible) { console.log('Add Story modal is not visible, skipping map update'); return; } // Check if the Location tab is active AND visible (map div only exists in this tab) const locationTab = document.getElementById('content-location'); const isLocationTabActive = locationTab && locationTab.classList.contains('active'); const isLocationTabVisible = locationTab && locationTab.classList.contains('show'); if (!isLocationTabActive) { console.log('Location tab is not active. Coordinates saved, map will update when you switch to Location tab.'); // Coordinates are already set in the form, map will be visible when user switches tabs return; } if (!isLocationTabVisible) { console.log('⏳ Location tab is active but not yet visible (transition in progress). Skipping map initialization.'); // Tab is transitioning, don't initialize map yet return; } // Check if map exists and is valid if (!window.addStoryMap || !isValidLeafletMap(window.addStoryMap)) { console.log('Map not initialized or invalid - initializing now from loadNearbyLocations...'); // Clear invalid map if exists if (window.addStoryMap) { window.addStoryMap = null; } initializeAddStoryMap(); // Wait for map to initialize, then update location setTimeout(function() { if (isValidLeafletMap(window.addStoryMap)) { updateMapWithLocation(lat, lng); } else { console.error('Map failed to initialize properly'); } }, 300); } else { // Map exists and is valid, update immediately updateMapWithLocation(lat, lng); } console.log('=== SELECT LOCATION COMPLETED ==='); } // Calculate distance between two coordinates (Haversine formula) function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371; // Radius of the Earth in kilometers const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // Distance in kilometers } // Load and display nearby locations for Add Story modal - SERVER-SIDE async function loadNearbyLocationsForAddStory(lat, lng) { console.log('🗺️ Loading nearby locations from server for:', lat, lng); const nearbyContainer = document.getElementById('addStoryNearbyPlacesContainer'); const nearbyList = document.getElementById('addStoryNearbyPlaces'); const nearbyCount = document.getElementById('addStoryNearbyCount'); try { // Get selected location type from dropdown const locationTypeSelect = document.getElementById('addStoryLocationType'); const selectedLocationType = locationTypeSelect ? locationTypeSelect.value : ''; // Use radius query with expanded search area (2x radius) // Database returns all in larger bounding box, we filter client-side for geometric intersection const radiusMeters = 500; // 500 meters target radius let url = `api/locations/read.php?lat=${lat}&lng=${lng}&radius=${radiusMeters}&radius_limit=1000`; // Add location_type filter if a specific type is selected if (selectedLocationType && selectedLocationType !== '') { url += `&location_type=${encodeURIComponent(selectedLocationType)}`; console.log('📡 Fetching nearby locations for type:', selectedLocationType); } else { console.log('📡 Fetching nearby locations (all types)'); } console.log(' URL:', url); const response = await fetch(url); const data = await response.json(); if (!data.success || !data.records || data.records.length === 0) { console.log('No nearby locations found'); nearbyContainer.style.display = 'none'; if (window.addStoryNearbyMarkersLayer) { window.addStoryNearbyMarkersLayer.clearLayers(); } return; } console.log(`📦 Database returned ${data.records.length} locations in expanded search area`); // Helper: Check if a polygon intersects with the circle function polygonIntersectsCircle(coordinates, centerLat, centerLng, radiusMeters) { // Check if any point of the polygon is within the circle for (const coord of coordinates[0]) { const [pLng, pLat] = coord; const dist = calculateDistance(centerLat, centerLng, pLat, pLng); if (dist <= radiusMeters / 1000) { // distance is in km return true; } } return false; } // Filter and process locations based on geometry intersection const locationsWithDistance = data.records .map(loc => { const distance = parseFloat(loc.distance) / 1000; // Convert to km const geomType = loc.geometry_type ? loc.geometry_type.toUpperCase() : ''; const isPolygon = geomType.includes('POLYGON'); let includeLocation = false; if (isPolygon && loc.geometry) { // For polygons: check if ANY part intersects the circle try { const geom = JSON.parse(loc.geometry); includeLocation = polygonIntersectsCircle(geom.coordinates, lat, lng, radiusMeters); } catch (e) { console.warn('Failed to parse geometry for location', loc.location_id); // Fallback to center point distance includeLocation = distance <= (radiusMeters / 1000); } } else { // For points: use center point distance includeLocation = distance <= (radiusMeters / 1000); } return { ...loc, distance, includeLocation }; }) .filter(loc => loc.includeLocation) .sort((a, b) => a.distance - b.distance); console.log(`✅ Found ${locationsWithDistance.length} locations intersecting 500m circle`); console.log(` (filtered from ${data.records.length} in expanded search)`); if (locationsWithDistance.length > 0) { console.log('🏷️ First 5 locations:', locationsWithDistance.slice(0, 5).map(l => ({ id: l.location_id, name: l.location_name, distance_m: Math.round(l.distance * 1000), has_geometry: !!l.geometry, geometry_type: l.geometry_type }))); } if (locationsWithDistance.length === 0) { nearbyContainer.style.display = 'none'; if (window.addStoryNearbyMarkersLayer) { window.addStoryNearbyMarkersLayer.clearLayers(); } return; } nearbyContainer.style.display = 'block'; nearbyCount.textContent = locationsWithDistance.length; // Store for later use window.currentNearbyLocations = locationsWithDistance; // Clear previous nearby markers and search radius circle if (window.addStoryNearbyMarkersLayer) { console.log('Clearing previous nearby markers'); window.addStoryNearbyMarkersLayer.clearLayers(); } // Remove previous search radius circle if it exists if (window.addStorySearchRadiusCircle) { window.addStorySearchRadiusCircle.remove(); window.addStorySearchRadiusCircle = null; } // Check if map is initialized if (!window.addStoryMap || typeof window.addStoryMap.setView !== 'function') { console.warn('Map not initialized yet, cannot draw markers'); // Still show the list even if map isn't ready } else { // Draw search radius circle (500m) to visualize search area window.addStorySearchRadiusCircle = L.circle([lat, lng], { radius: 500, // 500 meters in meters (not degrees!) color: '#3b82f6', fillColor: '#3b82f6', fillOpacity: 0.1, weight: 2, dashArray: '5, 5' }).addTo(window.addStoryMap); console.log('✓ Drew 500m search radius circle'); // Create layer group for nearby markers if it doesn't exist if (!window.addStoryNearbyMarkersLayer) { console.log('Creating nearby markers layer'); window.addStoryNearbyMarkersLayer = L.layerGroup().addTo(window.addStoryMap); } // Initialize markers storage window.addStoryNearbyMarkers = {}; // Draw ALL nearby location geometries on map (within 500m radius circle) const mapLocations = locationsWithDistance; // Show ALL, not just first 50 // Count geometry types const geometryCounts = { polygons: 0, points: 0, invalid: 0 }; console.log(`📍 Drawing ALL ${mapLocations.length} geometries within 500m circle:`); mapLocations.forEach((location, index) => { const locLat = parseFloat(location.latitude); const locLng = parseFloat(location.longitude); if (!isNaN(locLat) && !isNaN(locLng)) { let marker; // Check if location has polygon geometry // ST_GeometryType returns 'ST_Polygon', 'ST_Point', etc. const geomType = location.geometry_type ? location.geometry_type.toUpperCase() : ''; const isPolygon = geomType.includes('POLYGON'); if (location.geometry && isPolygon) { try { // Parse GeoJSON geometry const geometry = typeof location.geometry === 'string' ? JSON.parse(location.geometry) : location.geometry; // Create polygon from GeoJSON coordinates if (geometry.type === 'Polygon' && geometry.coordinates && geometry.coordinates[0]) { const latlngs = geometry.coordinates[0].map(coord => [coord[1], coord[0]]); // Validate polygon - must have at least 3 unique points if (latlngs.length >= 3) { // Check if polygon is valid (not a line) const uniquePoints = new Set(latlngs.map(p => `${p[0]},${p[1]}`)); if (uniquePoints.size >= 3) { marker = L.polygon(latlngs, { fillColor: '#10b981', color: '#047857', weight: 1, opacity: 1, fillOpacity: 0.4 }); geometryCounts.polygons++; console.log(' ✓ Polygon:', location.location_name || 'Unnamed', `(${latlngs.length} points)`); } else { geometryCounts.invalid++; marker = L.circleMarker([locLat, locLng], { radius: 12, fillColor: '#10b981', color: '#fff', weight: 2, opacity: 0.8, fillOpacity: 0.6 // Very transparent to see map features }); } } else { geometryCounts.invalid++; marker = L.circleMarker([locLat, locLng], { radius: 12, fillColor: '#10b981', color: '#fff', weight: 2, opacity: 0.8, fillOpacity: 0.6 // Very transparent to see map features }); } } else { geometryCounts.invalid++; marker = L.circleMarker([locLat, locLng], { radius: 12, fillColor: '#10b981', color: '#fff', weight: 2, opacity: 0.8, fillOpacity: 0.6 // Very transparent to see map features }); } } catch (e) { console.warn(' ⚠️ Error parsing polygon:', e.message); geometryCounts.invalid++; marker = L.circleMarker([locLat, locLng], { radius: 12, fillColor: '#10b981', color: '#fff', weight: 2, opacity: 0.8, fillOpacity: 0.6 // Very transparent to see map features }); } } else { // No polygon geometry, use circle marker for point geometryCounts.points++; marker = L.circleMarker([locLat, locLng], { radius: 12, fillColor: '#10b981', color: '#fff', weight: 2, opacity: 0.8, fillOpacity: 0.6 // Very transparent to see map features }); } // Store marker reference window.addStoryNearbyMarkers[index] = marker; // Add tooltip with location info const distanceText = location.distance < 1 ? `${(location.distance * 1000).toFixed(0)}m` : `${location.distance.toFixed(2)}km`; // Handle unnamed locations in tooltip const tooltipName = location.location_name && location.location_name.trim() ? location.location_name.trim() : t('text_unnamed_with_id', 'Unnamed (ID: {id})').replace('{id}', location.location_id); const tooltipContent = `${tooltipName} (${distanceText})`; marker.bindTooltip(tooltipContent, { permanent: false, direction: 'top', className: 'custom-tooltip', offset: [0, -10] }); // Make marker/polygon clickable to select the location marker.on('click', function() { const locationIdInput = document.getElementById('addStoryLocationId'); const locationSearchInput = document.getElementById('addStoryLocationSearch'); const sidebarLocationName = document.getElementById('addStorySidebarLocationName'); const latInput = document.getElementById('addStoryLatitude'); const lngInput = document.getElementById('addStoryLongitude'); if (locationIdInput) locationIdInput.value = location.location_id; if (locationSearchInput) locationSearchInput.value = location.location_name; if (sidebarLocationName) { sidebarLocationName.textContent = location.location_name; // Update preview location if (typeof window.updateStoryPreviewLocation === 'function') { window.updateStoryPreviewLocation(); } } if (latInput) latInput.value = locLat; if (lngInput) lngInput.value = locLng; // Update main selected marker (doubled size) if (window.addStoryMarker) { window.addStoryMarker.setLatLng([locLat, locLng]); } else { window.addStoryMarker = L.circleMarker([locLat, locLng], { radius: 16, fillColor: '#1967d2', color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.8 }).addTo(window.addStoryMap); } // Center map on selected location window.addStoryMap.setView([locLat, locLng], 18); // Reload nearby locations loadNearbyLocationsForAddStory(locLat, locLng); }); // Add to layer group marker.addTo(window.addStoryNearbyMarkersLayer); } else { console.warn('Invalid coordinates for location:', location.location_name, locLat, locLng); } }); // Summary of what was drawn console.log('✅ Geometry Summary within 500m circle:'); console.log(` - ${geometryCounts.polygons} polygons drawn`); console.log(` - ${geometryCounts.points} points (circle markers) drawn`); console.log(` - ${geometryCounts.invalid} invalid/degenerate geometries (shown as points)`); console.log(` - Total: ${mapLocations.length} geometries displayed`); // Fit map bounds to the search radius circle (500m) if (window.addStorySearchRadiusCircle) { // Fit map to show the entire 500m circle with padding window.addStoryMap.fitBounds(window.addStorySearchRadiusCircle.getBounds(), { padding: [50, 50], // 50px padding on all sides maxZoom: 17 // Don't zoom in too much }); console.log('Map bounds adjusted to fit 500m search circle'); } } nearbyList.innerHTML = locationsWithDistance.map((location, index) => { const distanceText = location.distance < 1 ? `${(location.distance * 1000).toFixed(0)}m` : `${location.distance.toFixed(2)}km`; // Debug: Log the actual location data if (index === 0) { console.log('🔍 First nearby location full data:', location); console.log(' - location_name field:', location.location_name); console.log(' - location_name type:', typeof location.location_name); console.log(' - location_name_ar field:', location.location_name_ar); } // Handle all cases: null, undefined, empty string, whitespace-only const rawName = location.location_name; const rawNameAr = location.location_name_ar; const hasName = rawName && typeof rawName === 'string' && rawName.trim() !== ''; const hasNameAr = rawNameAr && typeof rawNameAr === 'string' && rawNameAr.trim() !== ''; // Use English name, fallback to Arabic name, then "Unnamed" const locationName = hasName ? rawName.trim() : hasNameAr ? rawNameAr.trim() : t('text_unnamed_location', 'Unnamed Location'); // Log if unnamed if (!hasName && !hasNameAr) { console.log(`⚠️ Location ${location.location_id} has no name (en or ar)`); } // Build location info - show coords if no city/neighborhood const locationInfo = location.city || location.neighborhood ? [location.neighborhood, location.city].filter(Boolean).join(', ') : `${parseFloat(location.latitude).toFixed(4)}, ${parseFloat(location.longitude).toFixed(4)}`; return `
${hasName ? locationName : `${locationName}`}
${locationInfo}
${distanceText}
`; }).join(''); // Add click handlers and hover effects to nearby location items nearbyList.querySelectorAll('.nearby-location-item-modal').forEach(item => { const itemIndex = parseInt(item.getAttribute('data-index')); const selectArea = item.querySelector('[data-action="select"]'); const editButton = item.querySelector('[data-action="edit"]'); // Hover effects - highlight corresponding marker/polygon selectArea.addEventListener('mouseenter', () => { const marker = window.addStoryNearbyMarkers && window.addStoryNearbyMarkers[itemIndex]; if (marker && typeof marker.setStyle === 'function') { // Check if it's a circle marker or polygon if (marker instanceof L.CircleMarker) { marker.setStyle({ radius: 16, fillColor: '#f59e0b', fillOpacity: 0.9 }); } else { // It's a polygon marker.setStyle({ fillColor: '#f59e0b', color: '#d97706', fillOpacity: 0.7, weight: 4, opacity: 1 }); } marker.openTooltip(); } }); selectArea.addEventListener('mouseleave', () => { const marker = window.addStoryNearbyMarkers && window.addStoryNearbyMarkers[itemIndex]; if (marker && typeof marker.setStyle === 'function') { // Check if it's a circle marker or polygon if (marker instanceof L.CircleMarker) { marker.setStyle({ radius: 12, fillColor: '#10b981', fillOpacity: 0.6 }); } else { // It's a polygon marker.setStyle({ fillColor: '#10b981', color: '#047857', fillOpacity: 0.4, weight: 1, opacity: 1 }); } marker.closeTooltip(); } }); // Select handler selectArea.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const locationId = item.getAttribute('data-location-id'); const locationName = item.getAttribute('data-location-name'); const lat = parseFloat(item.getAttribute('data-lat')); const lng = parseFloat(item.getAttribute('data-lng')); console.log('🎯 Nearby location SELECTED:', { id: locationId, name: locationName, lat: lat, lng: lng, index: itemIndex }); // Update all fields using the existing selectLocation logic const locationIdInput = document.getElementById('addStoryLocationId'); const locationSearchInput = document.getElementById('addStoryLocationSearch'); const sidebarLocationName = document.getElementById('addStorySidebarLocationName'); const latInput = document.getElementById('addStoryLatitude'); const lngInput = document.getElementById('addStoryLongitude'); if (locationIdInput) locationIdInput.value = locationId; if (locationSearchInput) locationSearchInput.value = locationName; if (sidebarLocationName) { sidebarLocationName.textContent = locationName; // Update preview location if (typeof window.updateStoryPreviewLocation === 'function') { window.updateStoryPreviewLocation(); } } if (latInput) latInput.value = lat; if (lngInput) lngInput.value = lng; // Update map marker (doubled size) if (window.addStoryMarker) { window.addStoryMarker.setLatLng([lat, lng]); } else if (window.addStoryMap) { window.addStoryMarker = L.circleMarker([lat, lng], { radius: 16, fillColor: '#1967d2', color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.8 }).addTo(window.addStoryMap); } // Center map on new location if (window.addStoryMap) { window.addStoryMap.setView([lat, lng], 18); } // Reload nearby locations with new coordinates loadNearbyLocationsForAddStory(lat, lng); }); // Edit button handler editButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const locationId = item.getAttribute('data-location-id'); const locationName = item.getAttribute('data-location-name'); const lat = parseFloat(item.getAttribute('data-lat')); const lng = parseFloat(item.getAttribute('data-lng')); // Open edit modal openEditLocationModal(locationId, locationName, lat, lng); }); }); console.log('Nearby locations loaded:', locationsWithDistance.length); } catch (error) { console.error('❌ Error loading nearby locations:', error); nearbyContainer.style.display = 'none'; if (window.addStoryNearbyMarkersLayer) { window.addStoryNearbyMarkersLayer.clearLayers(); } } } // Open edit location modal function openEditLocationModal(locationId, locationName, lat, lng) { const modal = new bootstrap.Modal(document.getElementById('editLocationModal')); document.getElementById('editLocationId').value = locationId; document.getElementById('editLocationName').value = locationName === t('text_unnamed_location', 'Unnamed Location') ? '' : locationName; document.getElementById('editLocationLat').value = lat.toFixed(6); document.getElementById('editLocationLng').value = lng.toFixed(6); modal.show(); } // Save edited location document.getElementById('saveEditedLocationBtn').addEventListener('click', async function() { const locationId = document.getElementById('editLocationId').value; const locationName = document.getElementById('editLocationName').value.trim(); if (!locationName) { showAlert(t('alert_enter_location_name', 'Please enter a location name'), 'warning'); return; } const button = this; button.disabled = true; button.innerHTML = '' + t('action_saving', 'Saving...'); try { const response = await fetch(`api/locations/update.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ location_id: locationId, location_name: locationName }) }); const result = await response.json(); if (result.success) { showAlert(t('alert_location_updated', 'Location updated successfully!'), 'success'); // Close modal after a short delay to show the message setTimeout(() => { bootstrap.Modal.getInstance(document.getElementById('editLocationModal')).hide(); }, 1500); // Refresh nearby locations if we have coordinates const latInput = document.getElementById('addStoryLatitude'); const lngInput = document.getElementById('addStoryLongitude'); if (latInput && lngInput && latInput.value && lngInput.value) { loadNearbyLocationsForAddStory(parseFloat(latInput.value), parseFloat(lngInput.value)); } // Reload all locations loadAllLocations(); } else { showAlert(t('alert_error_prefix', 'Error: ') + (result.message || t('alert_update_failed', 'Failed to update location')), 'danger'); } } catch (error) { console.error('Error updating location:', error); showAlert(t('alert_error_updating', 'Error updating location: ') + error.message, 'danger'); } finally { button.disabled = false; button.innerHTML = '' + t('action_save', 'Save'); } }); // Add event listeners to lat/lng fields to update nearby locations const latField = document.getElementById('addStoryLatitude'); const lngField = document.getElementById('addStoryLongitude'); if (latField && lngField) { let debounceTimer; const updateNearby = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { const lat = parseFloat(latField.value); const lng = parseFloat(lngField.value); if (!isNaN(lat) && !isNaN(lng)) { loadNearbyLocationsForAddStory(lat, lng); } }, 500); }; latField.addEventListener('input', updateNearby); lngField.addEventListener('input', updateNearby); } // Location Type change -> Disable District & Neighborhood for "point" type if (locationTypeSelect) { locationTypeSelect.addEventListener('change', function() { const locationType = this.value; if (locationType && locationType.toLowerCase() === 'point') { console.log('✓ Location Type = "point" → Disabling District & Neighborhood only'); // Disable and reset District & Neighborhood - fully non-interactive if (districtSelect) { districtSelect.disabled = true; districtSelect.selectedIndex = 0; districtSelect.style.opacity = '0.5'; districtSelect.style.cursor = 'not-allowed'; districtSelect.style.pointerEvents = 'none'; // Prevent any interaction } if (neighborhoodSelect) { neighborhoodSelect.disabled = true; neighborhoodSelect.selectedIndex = 0; neighborhoodSelect.style.opacity = '0.5'; neighborhoodSelect.style.cursor = 'not-allowed'; neighborhoodSelect.style.pointerEvents = 'none'; // Prevent any interaction } } else { console.log('✓ Location Type ≠ "point" → Re-enabling District & Neighborhood (if parent selected)'); // Re-enable District & Neighborhood based on cascade rules if (districtSelect) { districtSelect.style.opacity = '1'; districtSelect.style.cursor = 'pointer'; districtSelect.style.pointerEvents = 'auto'; // Re-enable interaction // Only enable if city is selected const citySelected = citySelect?.value && citySelect.value.trim() !== ''; districtSelect.disabled = !citySelected; } if (neighborhoodSelect) { neighborhoodSelect.style.opacity = '1'; neighborhoodSelect.style.cursor = 'pointer'; neighborhoodSelect.style.pointerEvents = 'auto'; // Re-enable interaction // Only enable if district is selected const districtSelected = districtSelect?.value && districtSelect.value.trim() !== ''; neighborhoodSelect.disabled = !districtSelected; } } // Reload nearby locations when location type changes (if coordinates are set) const latField = document.getElementById('addStoryLatitude'); const lngField = document.getElementById('addStoryLongitude'); if (latField && lngField && latField.value && lngField.value) { const lat = parseFloat(latField.value); const lng = parseFloat(lngField.value); if (!isNaN(lat) && !isNaN(lng)) { console.log('↻ Reloading nearby locations for new type:', locationType || 'all'); loadNearbyLocationsForAddStory(lat, lng); } } }); } // Update location search when any filter changes [locationTypeSelect, countrySelect, regionSelect, citySelect, districtSelect, neighborhoodSelect].forEach(select => { if (select) { select.addEventListener('change', updateLocationSearch); } }); // Setup location search autocomplete const locationSearchInput = document.getElementById('addStoryLocationSearch'); const locationResultsDiv = document.getElementById('addStoryLocationResults'); if (locationSearchInput && locationResultsDiv) { console.log('Setting up location search autocomplete'); // When user clicks on search input, show currently filtered locations locationSearchInput.addEventListener('click', function(e) { e.stopPropagation(); console.log('Search input clicked'); const locations = window.addStoryCurrentFilteredLocations || window.addStoryFilteredLocations || []; console.log(`Showing ${locations.length} locations on click`); if (locations.length > 0) { displayFilteredLocations(locations.slice(0, 50)); } }); // When user focuses on search input, show currently filtered locations locationSearchInput.addEventListener('focus', function() { console.log('Search input focused'); const locations = window.addStoryCurrentFilteredLocations || window.addStoryFilteredLocations || []; console.log(`Showing ${locations.length} locations on focus`); if (locations.length > 0) { displayFilteredLocations(locations.slice(0, 50)); } }); // When user types in search input - SERVER-SIDE SEARCH let searchTimeout; locationSearchInput.addEventListener('input', async function() { const query = this.value.trim(); console.log(`Search query: "${query}"`); clearTimeout(searchTimeout); if (query.length < 2) { // Show currently filtered locations if available const locations = window.addStoryCurrentFilteredLocations || []; if (locations.length > 0) { displayFilteredLocations(locations.slice(0, 50)); } else { locationResultsDiv.innerHTML = ''; locationResultsDiv.style.display = 'none'; } return; } // Debounce search to avoid too many API calls searchTimeout = setTimeout(async () => { try { // Build query with dropdown filters + search const queryParams = new URLSearchParams(); // Add dropdown filters if (locationTypeSelect?.value && locationTypeSelect.value.trim() !== '') { queryParams.append('location_type', locationTypeSelect.value); } if (countrySelect?.value && countrySelect.value.trim() !== '') { queryParams.append('country', countrySelect.value); } if (regionSelect?.value && regionSelect.value.trim() !== '') { queryParams.append('region', regionSelect.value); } if (citySelect?.value && citySelect.value.trim() !== '') { queryParams.append('city', citySelect.value); } if (districtSelect?.value && districtSelect.value.trim() !== '') { queryParams.append('district', districtSelect.value); } if (neighborhoodSelect?.value && neighborhoodSelect.value.trim() !== '') { queryParams.append('neighborhood', neighborhoodSelect.value); } // Add search term queryParams.append('search', query); queryParams.append('limit', '100'); const url = `api/locations/read.php?${queryParams.toString()}`; console.log('🔍 Searching server:', url); const response = await fetch(url); const data = await response.json(); if (data.success && data.records) { const matches = data.records; console.log(`Found ${matches.length} matches for "${query}"`); displayFilteredLocations(matches.slice(0, 50)); } else { locationResultsDiv.innerHTML = '
No locations found
'; locationResultsDiv.style.display = 'block'; } } catch (error) { console.error('❌ Error searching locations:', error); } }, 300); // 300ms debounce delay }); // Hide results when clicking outside - only if dropdown is visible document.addEventListener('click', function(e) { // Only process if dropdown is actually visible if (locationResultsDiv.style.display !== 'block') { return; } // Exclude photo upload zone from this handler const photoUploadZone = document.getElementById('addStoryPhotoUpload'); if (photoUploadZone && (e.target === photoUploadZone || photoUploadZone.contains(e.target))) { console.log('Location search: Click is on photo upload zone, ignoring'); return; } const clickedInsideSearch = locationSearchInput === e.target || locationSearchInput.contains(e.target); const clickedInsideResults = locationResultsDiv === e.target || locationResultsDiv.contains(e.target); if (!clickedInsideSearch && !clickedInsideResults) { console.log('Location search: Clicked outside, hiding results'); locationResultsDiv.style.display = 'none'; } }); } else { console.error('Location search input or results div not found!'); } } // Global variables to track edit mode window.editingStoryId = null; window.originalStoryUniqueId = null; // Function to load story data for editing window.loadStoryForEdit = async function(storyId) { try { console.log('Loading story for edit:', storyId); // Fetch story data const response = await fetch(`${window.API_BASE}api/stories/read_one.php?id=${storyId}`); const data = await response.json(); if (!data || !data.story_id) { showAlert(t('error_load_story', 'Failed to load story data'), 'danger'); return; } // Set edit mode window.editingStoryId = storyId; window.originalStoryUniqueId = data.story_unique_id; // Open the Add Story modal const addStoryModal = document.getElementById('addStoryModal'); const bsModal = new bootstrap.Modal(addStoryModal); // Wait for modal to be shown before populating addStoryModal.addEventListener('shown.bs.modal', function onShown() { console.log('Populating form with story data:', data); // Populate form fields document.getElementById('addStoryTitle').value = data.title || ''; document.getElementById('addStoryHighlight').value = data.highlight || ''; // Load description into Quill editor - MUST happen AFTER all other field population // Store description for later loading window.pendingDescription = data.description || ''; console.log('📝 Scheduled description loading...'); console.log(' - Description from API:', data.description?.substring(0, 100)); // Set timeline - with small delay to ensure dropdown is rendered setTimeout(() => { const timelineField = document.getElementById('addStoryTimeline'); console.log('🔍 Timeline Field Debug:'); console.log(' - Field exists:', !!timelineField); console.log(' - Data timeline value:', data.timeline); console.log(' - Field options:', timelineField ? Array.from(timelineField.options).map(o => o.value) : 'N/A'); if (timelineField) { if (data.timeline) { timelineField.value = data.timeline; console.log(' - Field value after setting:', timelineField.value); console.log(' - Selected index:', timelineField.selectedIndex); if (timelineField.value !== data.timeline) { console.error('❌ Timeline value mismatch! Expected:', data.timeline, 'Got:', timelineField.value); } else { console.log('✅ Timeline successfully set to:', data.timeline); } timelineField.dispatchEvent(new Event('change', { bubbles: true })); } } }, 200); // Set relation - with small delay to ensure dropdown is rendered setTimeout(() => { const relationField = document.getElementById('addStoryRelation'); console.log('🔍 Relation Field Debug:'); console.log(' - Field exists:', !!relationField); console.log(' - Data relation value:', data.relation); console.log(' - Field options:', relationField ? Array.from(relationField.options).map(o => o.value) : 'N/A'); if (relationField) { if (data.relation) { relationField.value = data.relation; console.log(' - Field value after setting:', relationField.value); console.log(' - Selected index:', relationField.selectedIndex); if (relationField.value !== data.relation) { console.error('❌ Relation value mismatch! Expected:', data.relation, 'Got:', relationField.value); } else { console.log('✅ Relation successfully set to:', data.relation); } relationField.dispatchEvent(new Event('change', { bubbles: true })); } } }, 200); // Set sources const sourcesField = document.getElementById('addStorySources'); if (sourcesField) { sourcesField.value = data.sources || ''; console.log('Sources set to:', data.sources); } // Set time const timeField = document.getElementById('addStoryTime'); if (timeField) { timeField.value = data.story_time || ''; console.log('Story time set to:', data.story_time); } // Set location data - always set the values const locationIdField = document.getElementById('addStoryLocationId'); const latitudeField = document.getElementById('addStoryLatitude'); const longitudeField = document.getElementById('addStoryLongitude'); if (locationIdField) { locationIdField.value = data.location_id || ''; console.log('Location ID set to:', data.location_id); } if (latitudeField) { latitudeField.value = data.latitude || ''; console.log('Latitude set to:', data.latitude); } if (longitudeField) { longitudeField.value = data.longitude || ''; console.log('Longitude set to:', data.longitude); } // Update sidebar location name if we have place_name const sidebarLocationName = document.getElementById('addStorySidebarLocationName'); if (sidebarLocationName) { if (data.place_name) { sidebarLocationName.textContent = data.place_name; console.log('Location name set to:', data.place_name); } else { sidebarLocationName.textContent = 'لم يتم تحديد موقع'; } } // Load location on map in edit mode if (data.latitude && data.longitude) { console.log('📍 Loading location on map for edit mode...'); setTimeout(() => { const map = window.addStoryMap; if (map) { // Pan map to the location map.flyTo([data.latitude, data.longitude], 16); console.log('✅ Map centered on location:', data.latitude, data.longitude); // If there's a location_id, fetch nearby places to show it in the list if (data.location_id) { console.log('📍 Fetching location details for location_id:', data.location_id); fetch(`api/locations/read.php?lat=${data.latitude}&lon=${data.longitude}&radius=100`) .then(response => response.json()) .then(result => { if (result.status === 'success' && result.data) { console.log('✅ Found nearby locations:', result.data.length); // Display nearby locations const nearbyList = document.getElementById('addStoryNearbyPlaces'); if (nearbyList && result.data.length > 0) { nearbyList.innerHTML = result.data.map((location, index) => { const locationName = location.name || location.type || 'Unnamed Location'; const isSelected = location.location_id == data.location_id; return `
${locationName} ${isSelected ? '✓' : ''}
${location.type || ''}
${location.distance ? location.distance.toFixed(0) + 'm' : ''}
`; }).join(''); console.log('✅ Nearby places displayed with selected location highlighted'); } } }) .catch(error => { console.error('Error loading nearby locations:', error); }); } } else { console.error('❌ Map not initialized yet'); } }, 500); } // Set group - with small delay to ensure dropdown is populated setTimeout(() => { const groupField = document.getElementById('addStoryGroup'); console.log('🔍 Group Field Debug:'); console.log(' - Field exists:', !!groupField); console.log(' - Data group_id value:', data.group_id); console.log(' - Field options:', groupField ? Array.from(groupField.options).map(o => ({ value: o.value, text: o.text })) : 'N/A'); if (groupField) { if (data.group_id) { groupField.value = data.group_id; console.log(' - Field value after setting:', groupField.value); console.log(' - Selected index:', groupField.selectedIndex); if (groupField.value != data.group_id) { console.error('❌ Group value mismatch! Expected:', data.group_id, 'Got:', groupField.value); } else { console.log('✅ Group successfully set to:', data.group_id); } groupField.dispatchEvent(new Event('change', { bubbles: true })); } } }, 300); // Load photo if exists if (data.photo_data || data.photo_url) { const photoPreview = document.getElementById('addStoryPhotoPreview'); const previewImage = document.getElementById('addStoryPreviewImage'); const previewContainer = document.getElementById('addStoryPreviewImageContainer'); const imageUrl = data.photo_data || data.photo_url; if (photoPreview) { photoPreview.innerHTML = ``; } if (previewImage && previewContainer) { previewImage.src = imageUrl; previewContainer.style.display = 'block'; } } // Change modal title document.getElementById('addStoryModalLabel').textContent = t('text_edit_story', 'Edit Story'); // Change submit button text const submitBtn = document.getElementById('addStorySubmitBtn'); const submitBtnText = document.getElementById('addStorySubmitBtnText'); if (submitBtnText) { submitBtnText.textContent = t('action_update_story', 'Update Story'); } console.log('✓ Story loaded for editing:', data); console.log('✓ Details tab fields populated:', { timeline: data.timeline, relation: data.relation, sources: data.sources ? data.sources.substring(0, 50) + '...' : null, story_time: data.story_time, group_id: data.group_id }); // Remove event listener first addStoryModal.removeEventListener('shown.bs.modal', onShown); // Load description - try immediate execution with the exact method you suggested console.log('📝 Attempting to load description into Quill editor...'); console.log(' - Description:', data.description?.substring(0, 100)); if (window.addStoryQuillEditor && data.description) { // Use your suggested method: dangerouslyPasteHTML with position 0 console.log(' 🔧 Using: quill.clipboard.dangerouslyPasteHTML(0, html)'); try { window.addStoryQuillEditor.clipboard.dangerouslyPasteHTML(0, data.description); console.log(' ✅ dangerouslyPasteHTML executed successfully'); } catch (error) { console.error(' ❌ dangerouslyPasteHTML failed:', error.message); // Fallback to direct innerHTML console.log(' 🔧 Fallback: Using root.innerHTML'); window.addStoryQuillEditor.root.innerHTML = data.description; } // Update hidden input const hiddenInput = document.getElementById('addStoryDescription'); if (hiddenInput) { hiddenInput.value = data.description; console.log(' ✅ Hidden input updated'); } console.log(' ✅ Description loading completed'); console.log(' - Current editor HTML:', window.addStoryQuillEditor.root.innerHTML.substring(0, 200)); console.log(' - Current text:', window.addStoryQuillEditor.getText().substring(0, 100)); console.log(' - Current text length:', window.addStoryQuillEditor.getText().length); } else { console.error('❌ Cannot load description'); console.error(' - Editor exists?', !!window.addStoryQuillEditor); console.error(' - Description exists?', !!data.description); } }, { once: true }); bsModal.show(); } catch (error) { console.error('Error loading story:', error); showAlert(t('error_load_story', 'Failed to load story data'), 'danger'); } }; // Initialize real-time preview for Add Story modal function initializeStoryPreview() { const titleInput = document.getElementById('addStoryTitle'); const highlightInput = document.getElementById('addStoryHighlight'); const descriptionEditorDiv = document.getElementById('addStoryDescriptionEditor'); const descriptionHiddenInput = document.getElementById('addStoryDescription'); const timeInput = document.getElementById('addStoryTime'); const photoInput = document.getElementById('addStoryPhotoInput'); const previewTitle = document.getElementById('addStoryPreviewTitle'); const previewLocation = document.getElementById('addStoryPreviewLocation'); const previewHighlight = document.getElementById('addStoryPreviewHighlight'); const previewTime = document.getElementById('addStoryPreviewTime'); const previewUser = document.getElementById('addStoryPreviewUser'); const previewImage = document.getElementById('addStoryPreviewImage'); const previewImageContainer = document.getElementById('addStoryPreviewImageContainer'); // Initialize Quill Editor if (typeof Quill !== 'undefined' && descriptionEditorDiv) { window.addStoryQuillEditor = new Quill('#addStoryDescriptionEditor', { theme: 'snow', placeholder: 'Tell your story...', modules: { toolbar: [ [{ 'header': [1, 2, 3, false] }], ['bold', 'italic', 'underline'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['link'], ['clean'] ] } }); // Update hidden input when content changes window.addStoryQuillEditor.on('text-change', function() { const content = window.addStoryQuillEditor.root.innerHTML; if (descriptionHiddenInput) { descriptionHiddenInput.value = content; } }); console.log('Quill editor initialized'); } else { console.warn('Quill library not loaded or editor div not found'); } // Update title in real-time if (titleInput && previewTitle) { titleInput.addEventListener('input', function() { const value = this.value.trim(); previewTitle.textContent = value || t('text_preview_title_placeholder', 'Your story title will appear here...'); previewTitle.style.color = value ? '' : '#9ca3af'; // Auto-generate Story ID from title (supports all languages) const storyIdInput = document.getElementById('addStoryId'); if (storyIdInput && value) { // Generate slug: supports Unicode (Arabic, Chinese, etc.) const slug = value .toLowerCase() .trim() // Remove only punctuation and special symbols, keep all letters/numbers from any language .replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, '') .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Replace multiple hyphens with single .replace(/^-|-$/g, '') // Remove leading/trailing hyphens .substring(0, 50); // Limit to 50 characters // Add timestamp-based unique suffix const uniqueId = slug + '-' + Date.now().toString(36).slice(-6); storyIdInput.value = uniqueId; } else if (storyIdInput) { storyIdInput.value = ''; } }); } // Update highlight in real-time (from highlight field or description) if (highlightInput && previewHighlight) { highlightInput.addEventListener('input', function() { const value = this.value.trim(); if (value) { previewHighlight.textContent = value.substring(0, 150) + (value.length > 150 ? '...' : ''); previewHighlight.style.color = ''; } else { // Fall back to description if no highlight updateHighlightFromDescription(); } }); } // Update highlight from Quill editor if no explicit highlight function updateHighlightFromDescription() { if (window.addStoryQuill && previewHighlight) { const text = window.addStoryQuill.getText().trim(); const preview = text.substring(0, 150) + (text.length > 150 ? '...' : ''); previewHighlight.textContent = preview || t('text_preview_description_placeholder', 'Your story description will appear here. Start typing to see it come to life...'); previewHighlight.style.color = text ? '' : '#9ca3af'; } } // Update highlight from Quill editor (if available) if (descriptionEditorDiv && previewHighlight) { // Wait for Quill to initialize const checkQuill = setInterval(function() { if (window.addStoryQuill) { window.addStoryQuill.on('text-change', function() { // Only update if there's no explicit highlight const highlightValue = highlightInput ? highlightInput.value.trim() : ''; if (!highlightValue) { updateHighlightFromDescription(); } }); clearInterval(checkQuill); } }, 100); } // Update location when selected if (previewLocation) { const locationNameElement = document.getElementById('addStorySidebarLocationName'); // Helper function to update preview location function updatePreviewLocation() { if (!locationNameElement || !previewLocation) return; const locationText = locationNameElement.textContent.trim(); const noLocationText = t('text_no_location_selected', 'No location selected'); if (locationText && locationText !== noLocationText) { previewLocation.textContent = locationText; previewLocation.style.color = ''; } else { previewLocation.textContent = noLocationText; previewLocation.style.color = '#9ca3af'; } } // Make function globally accessible so it can be called when location changes window.updateStoryPreviewLocation = updatePreviewLocation; // Set initial value updatePreviewLocation(); // Watch for changes using MutationObserver if (locationNameElement) { const observer = new MutationObserver(function(mutations) { updatePreviewLocation(); }); observer.observe(locationNameElement, { childList: true, characterData: true, subtree: true }); } } // Update time in real-time if (timeInput && previewTime) { timeInput.addEventListener('change', function() { if (this.value) { const date = new Date(this.value); const now = new Date(); const diffTime = Math.abs(now - date); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); let timeAgo; if (diffDays < 1) { timeAgo = 'Today'; } else if (diffDays < 7) { timeAgo = `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`; } else if (diffDays < 30) { const weeks = Math.floor(diffDays / 7); timeAgo = `${weeks} week${weeks !== 1 ? 's' : ''} ago`; } else if (diffDays < 365) { const months = Math.floor(diffDays / 30); timeAgo = `${months} month${months !== 1 ? 's' : ''} ago`; } else { const years = Math.floor(diffDays / 365); timeAgo = `${years} year${years !== 1 ? 's' : ''} ago`; } previewTime.textContent = timeAgo; } else { previewTime.textContent = t('text_no_date', 'No date'); } }); } // Update user (use session user name) if (previewUser && typeof sessionUserName !== 'undefined' && sessionUserName) { previewUser.textContent = sessionUserName; } // Note: Photo preview is now handled in initializePhotoUpload() console.log('Story preview initialized'); } // Load groups table (dropdown is handled by app.js) async function loadGroupsTable() { const groupsList = document.getElementById('groupsList'); if (!groupsList) { console.error('groupsList element not found'); return; } try { console.log('Loading groups table...'); const response = await fetch(window.API_BASE + 'api/groups/read.php'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('Groups table data loaded:', data); // Clear table groupsList.innerHTML = ''; if (data.records && data.records.length > 0) { // Create table const table = document.createElement('table'); table.className = 'table table-hover'; // Create table header table.innerHTML = ` Group Name Stories `; const tbody = table.querySelector('tbody'); // Populate table data.records.forEach(group => { const row = document.createElement('tr'); const tooltipText = group.group_description || 'No description'; row.innerHTML = ` ${group.group_name} ${group.story_count || 0} `; tbody.appendChild(row); }); groupsList.appendChild(table); console.log('Groups table populated with', data.records.length, 'groups'); } else { groupsList.innerHTML = '
' + t('text_no_groups_found', 'No groups found') + '
'; } } catch (error) { console.error('Error loading groups table:', error); groupsList.innerHTML = '
' + t('error_loading_groups', 'Error loading groups') + '
'; } } // Note: Groups table is now loaded in initializeLocationDropdowns() when modal opens // REMOVED: Create group button handler - now handled in app.js to avoid duplicate event listeners // The createGroupBtn is handled by app.js with proper duplicate prevention // Initialize Submit Story functionality function initializeSubmitStory() { console.log('=========================================='); console.log('=== initializeSubmitStory called ==='); console.log('=========================================='); const submitBtn = document.getElementById('submitStoryBtn'); const submitSpinner = document.getElementById('submitSpinner'); const submitBtnText = document.getElementById('submitBtnText'); const alertContainer = document.getElementById('addStoryAlertContainer'); console.log('Submit button element:', submitBtn); console.log('Submit button found:', !!submitBtn); console.log('Submit spinner found:', !!submitSpinner); console.log('Submit button text found:', !!submitBtnText); console.log('Alert container found:', !!alertContainer); if (!submitBtn) { console.error('ERROR: Submit story button not found!'); return; } console.log('Adding click event listener to submit button...'); submitBtn.addEventListener('click', async function(e) { console.log('========================================'); console.log('SUBMIT STORY BUTTON CLICKED'); console.log('Event:', e); console.log('========================================'); // DEBUG: Check elements exist at click time const btnTextNow = document.getElementById('submitBtnText'); const spinnerNow = document.getElementById('submitSpinner'); console.log('🔍 AT CLICK TIME:'); console.log(' submitBtnText element:', btnTextNow); console.log(' submitBtnText exists?', !!btnTextNow); console.log(' submitSpinner element:', spinnerNow); console.log(' submitSpinner exists?', !!spinnerNow); console.log(' Button innerHTML:', submitBtn.innerHTML); // IMMEDIATE VISUAL FEEDBACK - Show button is working submitBtn.disabled = true; console.log('✓ Button disabled'); if (btnTextNow) { console.log('🔄 Changing button text to Processing...'); btnTextNow.innerHTML = '' + t('action_processing', 'Processing...'); console.log('✓ Button text changed. New innerHTML:', btnTextNow.innerHTML); } else { console.error('❌ submitBtnText NOT FOUND - changing entire button text'); submitBtn.innerHTML = '' + t('action_processing', 'Processing...'); } // Clear previous alerts if (alertContainer) { alertContainer.innerHTML = ''; } // Small delay to ensure UI updates await new Promise(resolve => setTimeout(resolve, 50)); // Collect form data console.log('Collecting form data...'); const title = document.getElementById('addStoryTitle')?.value?.trim(); const highlight = document.getElementById('addStoryHighlight')?.value?.trim(); const descriptionEditor = window.addStoryQuillEditor; // Quill editor instance const description = descriptionEditor ? descriptionEditor.root.innerHTML : ''; console.log('📝 Description collection debug:'); console.log(' - window.addStoryQuillEditor exists?', !!window.addStoryQuillEditor); console.log(' - window.addStoryQuillEditor VALUE:', window.addStoryQuillEditor); console.log(' - descriptionEditor exists?', !!descriptionEditor); console.log(' - descriptionEditor VALUE:', descriptionEditor); console.log(' - root exists?', !!descriptionEditor?.root); console.log(' - getText method exists?', typeof descriptionEditor?.getText); if (descriptionEditor?.getText) { console.log(' - getText() result:', descriptionEditor.getText()); console.log(' - getText() trimmed:', descriptionEditor.getText().trim()); console.log(' - getText() length:', descriptionEditor.getText().trim().length); } console.log(' - root.innerHTML:', descriptionEditor?.root?.innerHTML?.substring(0, 200)); console.log(' - description variable:', description?.substring(0, 200)); console.log(' - description length:', description?.length); const locationId = document.getElementById('addStoryLocationId')?.value; const longitude = document.getElementById('addStoryLongitude')?.value; const latitude = document.getElementById('addStoryLatitude')?.value; const photoInput = document.getElementById('addStoryPhotoInput'); const photoCaption = document.getElementById('addStoryPhotoCaption')?.value?.trim(); const groupId = document.getElementById('addStoryGroup')?.value; const timeline = document.getElementById('addStoryTimeline')?.value; const relation = document.getElementById('addStoryRelation')?.value; const sources = document.getElementById('addStorySources')?.value?.trim(); const storyTime = document.getElementById('addStoryTime')?.value; console.log('Form data collected:', { title, highlight, description: description?.substring(0, 50), locationId, longitude, latitude, groupId, timeline, relation, sources: sources?.substring(0, 50), storyTime, hasPhoto: !!(photoInput?.files?.[0]) }); console.log('📍 Location data:', { location_id: locationId, latitude: latitude, longitude: longitude, note: 'All three fields will be saved when location is selected' }); // Validate required fields console.log('Validating required fields...'); if (!title) { console.log('Validation failed: No title'); showAlert(t('alert_enter_story_title', 'Please enter a story title'), 'danger'); resetButton(); return; } if (!highlight) { console.log('Validation failed: No highlight'); showAlert(t('alert_enter_story_highlight', 'Please enter a story highlight'), 'danger'); resetButton(); return; } // Check description - use Quill's getText() method for accurate text content let descriptionText = ''; if (descriptionEditor && descriptionEditor.getText) { descriptionText = descriptionEditor.getText().trim(); } else { // Fallback: strip HTML and check if there's actual text content descriptionText = description ? description.replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim() : ''; } console.log('🔍 Description validation:'); console.log(' - Editor exists:', !!descriptionEditor); console.log(' - HTML length:', description?.length); console.log(' - Text content (via getText):', descriptionText); console.log(' - Text content length:', descriptionText.length); console.log(' - HTML preview:', description?.substring(0, 100)); // Check for empty description - Quill returns "\n" for empty editor const hasContent = descriptionText && descriptionText.length > 1; // More than just newline if (!hasContent) { console.log('❌ Validation failed: No description'); console.log(' Full HTML:', description); console.log(' Quill text:', descriptionText); showAlert(t('alert_enter_story_description', 'Please enter a story description'), 'danger'); resetButton(); return; } console.log('✓ Description validation passed'); if (!longitude || !latitude) { console.log('Validation failed: No location'); showAlert(t('alert_select_location', 'Please select a location'), 'danger'); resetButton(); return; } if (!timeline) { console.log('Validation failed: No timeline'); showAlert(t('alert_select_timeline', 'Please select a timeline'), 'danger'); resetButton(); return; } console.log('Validation passed!'); // Determine if we're in edit mode const isEditMode = window.editingStoryId !== null; // Generate unique ID from title (slug format) for new stories only let storyUniqueId; if (isEditMode) { // In edit mode, keep the original story_unique_id storyUniqueId = window.originalStoryUniqueId; console.log('ℹ️ Edit mode - using original story_unique_id:', storyUniqueId); } else { // For new stories, generate from title storyUniqueId = title .toLowerCase() .replace(/[^a-z0-9\u0600-\u06FF]/g, '-') // Keep Arabic, English, numbers .replace(/-+/g, '-') // Replace multiple dashes with one .replace(/^-|-$/g, ''); // Trim dashes from start/end console.log('✅ Generated new story_unique_id:', storyUniqueId); } // Skip duplicate ID check - database UNIQUE constraint will prevent duplicates // (check-id.php is often blocked by ModSecurity anyway) console.log('ℹ️ Skipping duplicate ID check - database will enforce uniqueness'); // Prepare story data const storyData = { csrf_token: CSRF_TOKEN, // Add CSRF token for security story_unique_id: storyUniqueId, title: title, description: description, highlight: highlight || null, longitude: longitude ? parseFloat(longitude) : null, latitude: latitude ? parseFloat(latitude) : null, location_id: locationId ? parseInt(locationId) : null, group_id: groupId ? parseInt(groupId) : null, story_time: storyTime || null, timeline: timeline || null, relation: relation || null, sources: sources || null, user_id: sessionUserId || null, // Add current user ID is_active: 1 // Make sure story is active }; console.log('✅ storyData object created:'); console.log(' - story_unique_id:', storyData.story_unique_id); console.log(' - title:', storyData.title); console.log(' - location_id:', storyData.location_id); console.log(' - group_id:', storyData.group_id); console.log(' - latitude:', storyData.latitude); console.log(' - longitude:', storyData.longitude); console.log(' - timeline:', storyData.timeline); console.log(' - is_active:', storyData.is_active); console.log(' - user_id:', storyData.user_id); // Handle photo upload if exists console.log('Checking for photo...'); if (photoInput && photoInput.files && photoInput.files[0]) { console.log('Photo found, reading file...'); const file = photoInput.files[0]; console.log('File info:', { name: file.name, size: file.size, type: file.type }); // Check file size (max 10MB) if (file.size > 10 * 1024 * 1024) { showAlert('Image file is too large. Please select an image smaller than 10MB.', 'danger'); resetButton(); return; } const reader = new FileReader(); reader.onloadend = async function() { console.log('Photo file read complete, compressing...'); // Compress image before sending const img = new Image(); img.onload = async function() { // Resize image if too large (max 1920px width/height) const maxDimension = 1920; let width = img.width; let height = img.height; if (width > maxDimension || height > maxDimension) { if (width > height) { height = (height / width) * maxDimension; width = maxDimension; } else { width = (width / height) * maxDimension; height = maxDimension; } } // Create canvas and compress const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); // Convert to JPEG with 70% quality for better compression const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.70); console.log(`Image compressed: original ${file.size} bytes, compressed ~${Math.round(compressedDataUrl.length * 0.75)} bytes`); // Convert compressed canvas to Blob for upload const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.70)); console.log('✅ Compressed blob size:', blob.size, 'bytes'); // Upload COMPRESSED photo as FILE try { console.log('🖼️ Uploading compressed photo:', blob.size, 'bytes (was', file.size, 'bytes)'); const formData = new FormData(); formData.append('photo', blob, 'photo.jpg'); const uploadResponse = await fetch('api/utils/photo_upload.php', { method: 'POST', body: formData }); console.log('📡 Photo upload response status:', uploadResponse.status); const uploadText = await uploadResponse.text(); console.log('📄 Photo upload raw response:', uploadText); const uploadResult = JSON.parse(uploadText); console.log('✅ Photo upload parsed:', uploadResult); if (uploadResult.success && uploadResult.url) { storyData.photo_url = uploadResult.url; storyData.photo_caption = photoCaption || null; console.log('🎉 Photo uploaded successfully:', uploadResult.url); console.log('📝 Photo caption:', photoCaption); await submitStory(storyData); } else { throw new Error(uploadResult.message || 'Upload failed'); } } catch (uploadError) { console.error('❌ Photo upload error:', uploadError); showAlert('Photo upload failed: ' + uploadError.message, 'danger'); resetButton(); return; } }; img.src = reader.result; }; reader.onerror = function(error) { console.error('FileReader error:', error); showAlert('Error reading photo file', 'danger'); resetButton(); }; reader.readAsDataURL(file); } else { console.log('No photo selected, submitting without photo'); await submitStory(storyData); } }); // Helper function to reset button state function resetButton() { console.log('🔄 resetButton() called'); submitBtn.disabled = false; const btnTextNow = document.getElementById('submitBtnText'); const spinnerNow = document.getElementById('submitSpinner'); if (spinnerNow) { spinnerNow.classList.add('d-none'); console.log('✓ Spinner hidden'); } if (btnTextNow) { btnTextNow.innerHTML = 'إرسال القصة'; console.log('✓ Button text reset to:', btnTextNow.innerHTML); } else { console.error('❌ submitBtnText NOT FOUND in resetButton - resetting entire button'); submitBtn.innerHTML = 'إرسال القصة'; } } async function submitStory(storyData) { console.log('========================================'); console.log('submitStory() CALLED'); console.log('========================================'); console.log('🔍 RECEIVED storyData parameter:'); console.log(' story_unique_id:', storyData.story_unique_id); console.log(' Has story_unique_id?:', 'story_unique_id' in storyData); console.log(' story_unique_id value:', storyData.story_unique_id); console.log(' story_unique_id type:', typeof storyData.story_unique_id); try { console.log('Setting loading state...'); const btnTextNow = document.getElementById('submitBtnText'); const spinnerNow = document.getElementById('submitSpinner'); // Show loading state submitBtn.disabled = true; if (spinnerNow) { spinnerNow.classList.remove('d-none'); console.log('✓ Spinner shown'); } if (btnTextNow) { btnTextNow.innerHTML = '' + t('action_submitting', 'Submitting...'); console.log('✓ Button text set to Submitting...'); } else { console.error('❌ submitBtnText NOT FOUND in submitStory'); } console.log('Story data to submit:', storyData); console.log('📤 JSON BEING SENT:'); console.log(JSON.stringify(storyData, null, 2)); // Determine if we're creating or updating const isEditMode = window.editingStoryId !== null; const apiEndpoint = isEditMode ? window.API_BASE + 'api/stories/update.php?id=' + window.editingStoryId : window.API_BASE + 'api/stories/create-new.php'; const httpMethod = 'POST'; // Always POST, PHP files handle it internally console.log('Mode:', isEditMode ? 'UPDATE' : 'CREATE'); console.log('Making fetch request to:', apiEndpoint); console.log('HTTP Method:', httpMethod); // Add story_id if editing if (isEditMode) { storyData.story_id = window.editingStoryId; storyData.user_id = sessionUserId; } const response = await fetch(apiEndpoint, { method: httpMethod, credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, body: JSON.stringify(storyData) }); console.log('Response received:', response); console.log('Response status:', response.status); console.log('Response OK:', response.ok); const result = await response.json(); console.log('📥 RESPONSE FROM API:'); console.log(result); if (response.ok || result.success) { const message = isEditMode ? t('success_update_story', 'Story updated successfully!') : t('success_create_story', 'Story created successfully!'); showAlert(message, 'success'); // Reset edit mode window.editingStoryId = null; window.originalStoryUniqueId = null; console.log('✅ Story created/updated successfully!'); console.log('✅ Response:', result); // Wait a bit then close modal and redirect to main page setTimeout(function() { const modal = bootstrap.Modal.getInstance(document.getElementById('addStoryModal')); if (modal) { modal.hide(); } // Redirect to main page to show the new story window.location.href = '/'; }, 1500); } else { console.error('❌ Story creation/update failed'); console.error('❌ Response:', result); console.error('❌ HTTP Status:', response.status); const errorMsg = isEditMode ? t('error_update_story', 'Failed to update story') : t('error_create_story', 'Failed to create story'); showAlert('Error: ' + (result.message || result.error || errorMsg), 'danger'); } } catch (error) { console.error('========================================'); console.error('ERROR CAUGHT IN submitStory()'); console.error('Error object:', error); console.error('Error message:', error.message); console.error('Error stack:', error.stack); console.error('========================================'); showAlert('Error submitting story. Please try again. Error: ' + error.message, 'danger'); } finally { console.log('Resetting button state...'); resetButton(); console.log('Button state reset'); } } function showAlert(message, type) { console.log(`showAlert called: [${type}] ${message}`); if (alertContainer) { alertContainer.innerHTML = ` `; // Scroll to top to see alert const modalBody = document.querySelector('#addStoryModal .modal-body'); if (modalBody) modalBody.scrollTop = 0; } else { console.error('Alert container not found!'); } } console.log('=========================================='); console.log('Submit button event listener added!'); console.log('Button disabled?', submitBtn.disabled); console.log('Submit story functionality initialized'); console.log('=========================================='); // Add Clear Form functionality const clearFormBtn = document.getElementById('addStoryClearForm'); if (clearFormBtn) { clearFormBtn.addEventListener('click', function() { if (confirm(t('confirm_clear_form', 'Are you sure you want to clear all form fields?'))) { // Clear all text inputs document.getElementById('addStoryTitle').value = ''; document.getElementById('addStoryHighlight').value = ''; document.getElementById('addStoryLongitude').value = ''; document.getElementById('addStoryLatitude').value = ''; document.getElementById('addStoryLocationId').value = ''; document.getElementById('addStoryLocationSearch').value = ''; document.getElementById('addStorySources').value = ''; document.getElementById('addStoryTime').value = ''; document.getElementById('addStoryId').value = ''; // Clear Quill editor if (window.addStoryQuillEditor) { window.addStoryQuillEditor.setContents([]); } // Reset dropdowns document.getElementById('addStoryGroup').value = ''; document.getElementById('addStoryTimeline').value = ''; document.getElementById('addStoryRelation').value = ''; document.getElementById('addStoryLocationType').value = ''; document.getElementById('addStoryCountry').value = ''; document.getElementById('addStoryRegion').value = ''; document.getElementById('addStoryCity').value = ''; document.getElementById('addStoryDistrict').value = ''; document.getElementById('addStoryNeighborhood').value = ''; // Clear photo document.getElementById('addStoryPhotoInput').value = ''; const photoPreview = document.getElementById('addStoryPhotoPreview'); if (photoPreview) { photoPreview.innerHTML = ''; } // Clear photo caption const photoCaption = document.getElementById('addStoryPhotoCaption'); if (photoCaption) { photoCaption.value = ''; } // Clear location display const locationDisplay = document.getElementById('addStoryLocationDisplay'); if (locationDisplay) { locationDisplay.textContent = 'المحدد حاليًا: لا شيء'; } // Clear selected location in sidebar const sidebarLocationName = document.getElementById('addStorySidebarLocationName'); if (sidebarLocationName) { sidebarLocationName.textContent = t('text_no_location_selected', 'No location selected'); // Update preview location if (typeof window.updateStoryPreviewLocation === 'function') { window.updateStoryPreviewLocation(); } } // Clear nearby locations list const nearbyList = document.getElementById('addStoryNearbyPlaces'); const nearbyCount = document.getElementById('addStoryNearbyCount'); const nearbyContainer = document.getElementById('addStoryNearbyPlacesContainer'); if (nearbyList) { nearbyList.innerHTML = ''; } if (nearbyCount) { nearbyCount.textContent = '0'; } if (nearbyContainer) { nearbyContainer.style.display = 'none'; } // Clear map markers and polygons if (window.addStoryNearbyMarkersLayer) { window.addStoryNearbyMarkersLayer.clearLayers(); } // Reset map view to initial position (Gaza center) if (window.addStoryMap) { window.addStoryMap.setView([31.5, 34.466667], 12); } // Clear alerts alertContainer.innerHTML = ''; console.log('Form cleared - including map, markers, and nearby locations'); } }); } } // Load cities dynamically for cityDropdown async function loadCitiesDropdown() { console.log('🏙️ Loading cities for cityDropdown...'); try { const response = await fetch('api/locations/read.php?list=city'); console.log('City API response status:', response.status); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('City API data:', data); if (data.success && data.records && Array.isArray(data.records)) { const dropdownMenu = document.querySelector('#cityDropdown + .dropdown-menu'); if (!dropdownMenu) { console.error('City dropdown menu not found'); return; } // Clear existing items dropdownMenu.innerHTML = ''; // Add cities from API const validCities = data.records.filter(v => v && v.trim() !== ''); validCities.forEach(city => { const li = document.createElement('li'); const a = document.createElement('a'); a.className = 'dropdown-item'; a.href = `?city=${encodeURIComponent(city)}`; a.textContent = city; a.dataset.value = city; li.appendChild(a); dropdownMenu.appendChild(li); }); console.log('✅ Successfully loaded', validCities.length, 'cities into dropdown'); // Add click event listeners to city dropdown items dropdownMenu.addEventListener('click', function(e) { const dropdownItem = e.target.closest('.dropdown-item'); if (dropdownItem) { e.preventDefault(); const city = dropdownItem.dataset.value; console.log('🏙️ City selected:', city); // Update button text const cityDropdown = document.getElementById('cityDropdown'); if (cityDropdown) { const buttonText = cityDropdown.querySelector('span'); if (buttonText) { buttonText.textContent = city || 'City'; } } // Store city filter globally window.currentCityFilter = city; // Reload map polygons with city filter and current zoom level if (typeof MapFunctions !== 'undefined' && MapFunctions.loadGeoJSON) { const mainMap = AppState?.getMap('main'); const currentZoom = mainMap ? mainMap.getZoom() : null; console.log('🔄 Reloading map polygons for city:', city, 'zoom:', currentZoom); MapFunctions.loadGeoJSON(null, city, currentZoom); } } }); // Add "All Cities" option at the top const allCitiesLi = document.createElement('li'); const allCitiesLink = document.createElement('a'); allCitiesLink.className = 'dropdown-item'; allCitiesLink.href = '#'; allCitiesLink.textContent = 'All Cities'; allCitiesLink.dataset.value = ''; allCitiesLi.appendChild(allCitiesLink); dropdownMenu.insertBefore(allCitiesLi, dropdownMenu.firstChild); // Add divider after "All Cities" const divider = document.createElement('li'); divider.innerHTML = ''; dropdownMenu.insertBefore(divider, dropdownMenu.children[1]); // Ensure Bootstrap dropdown is initialized for city dropdown const cityDropdown = document.getElementById('cityDropdown'); if (cityDropdown && typeof bootstrap !== 'undefined' && bootstrap.Dropdown) { try { let dropdownInstance = bootstrap.Dropdown.getInstance(cityDropdown); if (!dropdownInstance) { console.log('🔧 Initializing Bootstrap dropdown for city dropdown...'); dropdownInstance = new bootstrap.Dropdown(cityDropdown); console.log('✅ City dropdown initialized successfully'); } } catch (error) { console.error('❌ Error initializing city dropdown:', error); } } } else { console.warn('⚠️ City API returned success=false or no records:', data); } } catch (error) { console.error('❌ Error loading cities for dropdown:', error); console.error('Stack trace:', error.stack); } } // Ensure category dropdowns are initialized (TOP and BOTTOM) function ensureCategoryDropdown() { console.log('📁 Ensuring category dropdowns are initialized...'); // Initialize TOP category dropdown (in story-type-section) const categoryDropdownTop = document.getElementById('categoryDropdownTop'); if (categoryDropdownTop) { console.log('✅ Found categoryDropdownTop in story-type-section'); if (typeof bootstrap !== 'undefined' && bootstrap.Dropdown) { try { let dropdownInstance = bootstrap.Dropdown.getInstance(categoryDropdownTop); if (!dropdownInstance) { console.log('🔧 Initializing Bootstrap dropdown for TOP category dropdown...'); dropdownInstance = new bootstrap.Dropdown(categoryDropdownTop); console.log('✅ TOP category dropdown initialized successfully'); } } catch (error) { console.error('❌ Error initializing TOP category dropdown:', error); } } // Add click handler for TOP category dropdown - SAME LOGIC AS app.js const topDropdownMenu = categoryDropdownTop.nextElementSibling; if (topDropdownMenu) { topDropdownMenu.addEventListener('click', function(e) { const dropdownItem = e.target.closest('.dropdown-item'); if (dropdownItem) { e.preventDefault(); // Get category value const category = dropdownItem.getAttribute('data-category'); const categoryIcon = dropdownItem.getAttribute('data-icon'); // Update active state topDropdownMenu.querySelectorAll('.dropdown-item').forEach(item => { item.classList.remove('active'); }); dropdownItem.classList.add('active'); // Update dropdown button text with icon if available const buttonText = categoryDropdownTop.querySelector('span'); const iconElement = categoryDropdownTop.querySelector('i'); if (category === '') { if (buttonText) buttonText.textContent = 'Categories'; if (iconElement) iconElement.className = 'fas fa-filter'; } else { if (buttonText) buttonText.textContent = dropdownItem.textContent.trim(); if (iconElement && categoryIcon) { iconElement.className = 'fas ' + categoryIcon; } } // Store in AppState if (typeof AppState !== 'undefined' && AppState.updateUIState) { AppState.updateUIState({ categoryFilter: category }); } // Trigger reload of stories with category filter console.log('Category filter changed to:', category); // Store category filter globally window.currentCategoryFilter = category; // Determine which tab is active and reload accordingly const activeTab = document.querySelector('.rz-tabview-nav-link.rz-state-active, #story-type-section .rz-state-active'); if (activeTab) { const tabType = activeTab.getAttribute('data-t'); console.log('Reloading tab with category filter:', tabType, category); // Call the appropriate load function from App if (typeof App !== 'undefined') { if (tabType === 'projects') { if (App.loadProjects) App.loadProjects(); } else if (tabType === 'stories') { if (App.loadStoriesOnly) App.loadStoriesOnly(); } else { // 'all' or default if (App.getAllStories) App.getAllStories(); } } else { console.error('App module not found'); } } } }); } } else { console.warn('⚠️ categoryDropdownTop not found'); } } // Initialize tab click handlers for All, Stories, Projects function initializeTabHandlers() { console.log('📑 Initializing tab handlers...'); const tabs = document.querySelectorAll('#story-type-section .btn-link[data-t]'); tabs.forEach(tab => { tab.addEventListener('click', function(e) { e.preventDefault(); // Remove active class from all tabs tabs.forEach(t => t.classList.remove('rz-state-active')); // Add active class to clicked tab this.classList.add('rz-state-active'); const tabType = this.getAttribute('data-t'); console.log('Tab clicked:', tabType); // Trigger the appropriate load function based on tab type if (typeof App !== 'undefined') { if (tabType === 'projects') { console.log('Loading projects...'); if (App.loadProjects) App.loadProjects(); } else if (tabType === 'stories') { console.log('Loading stories only...'); if (App.loadStoriesOnly) App.loadStoriesOnly(); } else if (tabType === 'all') { console.log('Loading all stories...'); if (App.getAllStories) App.getAllStories(); } } else { console.error('App module not found'); } }); }); console.log('✅ Tab handlers initialized for', tabs.length, 'tabs'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded fired - calling App.initialize()'); if (typeof App !== 'undefined' && App.initialize) { console.log('Starting App.initialize()...'); App.initialize(); } else { console.error('App module not found'); } // Initialize Add Story modal features initializeAddStoryTabs(); initializeLocationDropdowns(); initializeStoryPreview(); initializeSubmitStory(); // Load dropdowns loadCitiesDropdown(); ensureCategoryDropdown(); // Initialize tab handlers initializeTabHandlers(); }); } else { console.log('DOM already loaded - calling App.initialize()'); if (typeof App !== 'undefined' && App.initialize) { console.log('Starting App.initialize()...'); App.initialize(); } else { console.error('App module not found'); } // Initialize Add Story modal features initializeAddStoryTabs(); initializeLocationDropdowns(); initializeStoryPreview(); initializeSubmitStory(); // Load dropdowns loadCitiesDropdown(); ensureCategoryDropdown(); // Initialize tab handlers initializeTabHandlers(); } // Initialize Add Project form when modal is shown const addProjectModal = document.getElementById('addProjectModal'); if (addProjectModal) { addProjectModal.addEventListener('shown.bs.modal', function () { console.log('🚀 Add Project modal shown - initializing form...'); // Initialize the add-project form if (typeof window.initializeAddProjectForm === 'function') { window.initializeAddProjectForm(); console.log('✅ Add Project form initialized'); } else { console.warn('⚠️ initializeAddProjectForm function not found'); } }); } // Event listener for successful project creation window.addEventListener('projectCreated', function(event) { console.log('📢 Project created event received'); // Close the modal const modal = document.getElementById('addProjectModal'); if (modal) { const bootstrapModal = bootstrap.Modal.getInstance(modal); if (bootstrapModal) { bootstrapModal.hide(); } } // Reload projects if needed if (typeof App !== 'undefined' && App.loadProjects) { App.loadProjects(); } // Show success message if (typeof showAlert === 'function') { showAlert(t('success_project_created', 'Project created successfully!'), 'success'); } }); // ======================================== // LOCATION SEARCH IMPROVEMENTS // ======================================== /** * Initialize "Use My Location" button with geolocation */ function initializeLocationSearch() { const useMyLocationBtn = document.getElementById('useMyLocationBtn'); const searchInput = document.getElementById('searchInput'); if (useMyLocationBtn) { useMyLocationBtn.addEventListener('click', function() { console.log('🎯 Use My Location clicked'); // Check if geolocation is supported if (!navigator.geolocation) { showAlert(t('error_geolocation_not_supported', 'Geolocation is not supported by your browser'), 'danger'); return; } // Show loading state const originalHTML = this.innerHTML; this.disabled = true; this.innerHTML = ''; // Get current position navigator.geolocation.getCurrentPosition( async function(position) { const lat = position.coords.latitude; const lng = position.coords.longitude; const accuracy = position.coords.accuracy; console.log('📍 Got location:', { lat, lng, accuracy }); // Update search input searchInput.value = `${lat.toFixed(6)}, ${lng.toFixed(6)}`; // Search for nearby places await searchNearbyPlaces(lat, lng, accuracy); // Reset button state useMyLocationBtn.disabled = false; useMyLocationBtn.innerHTML = originalHTML; }, function(error) { console.error('❌ Geolocation error:', error); let errorMessage = ''; switch(error.code) { case error.PERMISSION_DENIED: errorMessage = t('error_location_permission_denied', 'Location permission denied. Please allow location access.'); break; case error.POSITION_UNAVAILABLE: errorMessage = t('error_location_unavailable', 'Location information is unavailable.'); break; case error.TIMEOUT: errorMessage = t('error_location_timeout', 'Location request timed out.'); break; default: errorMessage = t('error_location_unknown', 'An unknown error occurred while getting your location.'); break; } showAlert(errorMessage, 'danger'); // Reset button state useMyLocationBtn.disabled = false; useMyLocationBtn.innerHTML = originalHTML; }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); }); } } /** * Search for nearby places within a radius * @param {number} lat - Latitude * @param {number} lng - Longitude * @param {number} accuracy - GPS accuracy in meters */ async function searchNearbyPlaces(lat, lng, accuracy) { try { // Use 500m radius or GPS accuracy (whichever is larger) const radius = Math.max(500, Math.min(accuracy, 2000)); // Min 500m, max 2km console.log(`🔍 Searching within ${radius}m radius of (${lat}, ${lng})`); // Show search radius circle on map showSearchRadiusCircle(lat, lng, radius); // Call API to get nearby stories/locations const response = await fetch( `${window.API_BASE}api/stories/read.php?lat=${lat}&lng=${lng}&radius=${radius}`, { credentials: 'include' } ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('📊 Nearby search results:', data); if (data.records && data.records.length > 0) { showAlert( t('success_found_nearby', `Found ${data.records.length} location(s) within ${Math.round(radius)}m`), 'success' ); // Display results if (typeof App !== 'undefined' && App.displayStories) { App.displayStories(data.records, data.pagination); } // Fly to location on map const mainMap = AppState?.getMap('main'); if (mainMap) { mainMap.flyTo({ center: [lng, lat], zoom: 15, duration: 1500 }); } } else { showAlert( t('info_no_nearby_locations', 'No locations found nearby. Try increasing the search radius.'), 'info' ); } } catch (error) { console.error('❌ Error searching nearby:', error); showAlert(t('error_search_failed', 'Failed to search nearby locations'), 'danger'); } } /** * Show search radius circle on the map * @param {number} lat - Center latitude * @param {number} lng - Center longitude * @param {number} radius - Radius in meters */ function showSearchRadiusCircle(lat, lng, radius) { const mainMap = AppState?.getMap('main'); if (!mainMap) { console.warn('⚠️ Map not found, cannot show radius circle'); return; } console.log('🔵 Drawing search radius circle:', { lat, lng, radius }); // Remove existing circle if present if (mainMap.getLayer('search-radius-circle')) { mainMap.removeLayer('search-radius-circle'); } if (mainMap.getLayer('search-radius-border')) { mainMap.removeLayer('search-radius-border'); } if (mainMap.getSource('search-radius')) { mainMap.removeSource('search-radius'); } if (mainMap.getLayer('search-center-marker')) { mainMap.removeLayer('search-center-marker'); } if (mainMap.getSource('search-center')) { mainMap.removeSource('search-center'); } // Create circle geometry using manual calculation const center = [lng, lat]; const points = []; const steps = 64; const earthRadius = 6378137; // Earth's radius in meters for (let i = 0; i < steps; i++) { const angle = (i * 360) / steps; const radians = (angle * Math.PI) / 180; // Calculate offset in degrees const dx = (radius / earthRadius) * (180 / Math.PI) / Math.cos(lat * Math.PI / 180); const dy = (radius / earthRadius) * (180 / Math.PI); const pointLng = lng + dx * Math.cos(radians); const pointLat = lat + dy * Math.sin(radians); points.push([pointLng, pointLat]); } // Close the circle points.push(points[0]); // Add circle to map mainMap.addSource('search-radius', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'Polygon', coordinates: [points] } } }); mainMap.addLayer({ id: 'search-radius-circle', type: 'fill', source: 'search-radius', paint: { 'fill-color': '#4285F4', 'fill-opacity': 0.2 } }); // Add circle border mainMap.addLayer({ id: 'search-radius-border', type: 'line', source: 'search-radius', paint: { 'line-color': '#4285F4', 'line-width': 2, 'line-dasharray': [2, 2] } }); // Add center marker mainMap.addSource('search-center', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'Point', coordinates: center } } }); mainMap.addLayer({ id: 'search-center-marker', type: 'circle', source: 'search-center', paint: { 'circle-radius': 8, 'circle-color': '#4285F4', 'circle-stroke-width': 2, 'circle-stroke-color': '#FFFFFF' } }); console.log('✅ Search radius circle displayed'); } // Initialize location search when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeLocationSearch); } else { initializeLocationSearch(); }