diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 9170f534..203c0d53 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -32,6 +32,8 @@ export default class extends Controller { settingsButtonAdded = false; layerControl = null; + visitedCitiesCache = new Map(); + trackedMonthsCache = null; connect() { console.log("Map controller connected"); @@ -180,8 +182,16 @@ export default class extends Controller { const isPanelOpen = localStorage.getItem('mapPanelOpen') === 'true'; const hasDateParams = urlParams.has('start_at') && urlParams.has('end_at'); - if (isPanelOpen || hasDateParams) { - this.toggleRightPanel(); + // Always create the panel first + this.toggleRightPanel(); + + // Then hide it if it shouldn't be open + if (!isPanelOpen && !hasDateParams) { + const panel = document.querySelector('.leaflet-right-panel'); + if (panel) { + panel.style.display = 'none'; + localStorage.setItem('mapPanelOpen', 'false'); + } } } @@ -191,8 +201,7 @@ export default class extends Controller { } // Store panel state before disconnecting if (this.rightPanel) { - const finalState = this.rightPanel._map ? 'true' : 'false'; - + const finalState = document.querySelector('.leaflet-right-panel').style.display !== 'none' ? 'true' : 'false'; localStorage.setItem('mapPanelOpen', finalState); } this.map.remove(); @@ -952,16 +961,17 @@ export default class extends Controller { toggleRightPanel() { if (this.rightPanel) { - if (this.rightPanel._map) { - this.map.removeControl(this.rightPanel); - localStorage.setItem('mapPanelOpen', 'false'); - } else { - this.map.addControl(this.rightPanel); - localStorage.setItem('mapPanelOpen', 'true'); - // Fetch visited cities when panel is opened - this.fetchAndDisplayVisitedCities(); + const panel = document.querySelector('.leaflet-right-panel'); + if (panel) { + if (panel.style.display === 'none') { + panel.style.display = 'block'; + localStorage.setItem('mapPanelOpen', 'true'); + } else { + panel.style.display = 'none'; + localStorage.setItem('mapPanelOpen', 'false'); + } + return; } - return; } this.rightPanel = L.control({ position: 'topright' }); @@ -1004,7 +1014,7 @@ export default class extends Controller { class="btn btn-primary disabled ${month === currentMonth ? 'btn-active' : ''}" data-month-name="${month}" style="pointer-events: none; opacity: 0.6; color: rgb(116 128 255) !important;"> - ${month} + `).join('')} @@ -1013,123 +1023,7 @@ export default class extends Controller { `; - fetch(`/api/v1/points/tracked_months?api_key=${this.apiKey}`) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(yearsData => { - const yearSelect = document.getElementById('year-select'); - - if (!Array.isArray(yearsData) || yearsData.length === 0) { - yearSelect.innerHTML = ''; - return; - } - - // Check if the current year exists in the API response - const currentYearData = yearsData.find(yearData => yearData.year.toString() === currentYear); - - const options = yearsData - .filter(yearData => yearData && yearData.year) - .map(yearData => { - const months = Array.isArray(yearData.months) ? yearData.months : []; - const isCurrentYear = yearData.year.toString() === currentYear; - return ` - - `; - }) - .join(''); - - yearSelect.innerHTML = ` - - ${options} - `; - - const updateMonthLinks = (selectedYear, availableMonths) => { - // Get current date from URL parameters - const urlParams = new URLSearchParams(window.location.search); - const startDate = urlParams.get('start_at') ? new Date(urlParams.get('start_at')) : new Date(); - const endDate = urlParams.get('end_at') ? new Date(urlParams.get('end_at')) : new Date(); - - allMonths.forEach((month, index) => { - const monthLink = div.querySelector(`a[data-month-name="${month}"]`); - if (!monthLink) return; - - // Check if this month falls within the selected date range - const isSelected = startDate && endDate && - selectedYear === startDate.getFullYear().toString() && // Only check months for the currently selected year - isMonthInRange(index, startDate, endDate, parseInt(selectedYear)); - - if (availableMonths.includes(month)) { - monthLink.classList.remove('disabled'); - monthLink.style.pointerEvents = 'auto'; - monthLink.style.opacity = '1'; - - // Update the active state based on selection - if (isSelected) { - monthLink.classList.add('btn-active', 'btn-primary'); - } else { - monthLink.classList.remove('btn-active', 'btn-primary'); - } - - const monthNum = (index + 1).toString().padStart(2, '0'); - const startDate = `${selectedYear}-${monthNum}-01T00:00`; - const lastDay = new Date(selectedYear, index + 1, 0).getDate(); - const endDate = `${selectedYear}-${monthNum}-${lastDay}T23:59`; - - const href = `map?end_at=${encodeURIComponent(endDate)}&start_at=${encodeURIComponent(startDate)}`; - monthLink.setAttribute('href', href); - } else { - monthLink.classList.add('disabled'); - monthLink.classList.remove('btn-active', 'btn-primary'); - monthLink.style.pointerEvents = 'none'; - monthLink.style.opacity = '0.6'; - monthLink.setAttribute('href', '#'); - } - }); - }; - - // Helper function to check if a month falls within a date range - const isMonthInRange = (monthIndex, startDate, endDate, selectedYear) => { - // Create date objects for the first and last day of the month in the selected year - const monthStart = new Date(selectedYear, monthIndex, 1); - const monthEnd = new Date(selectedYear, monthIndex + 1, 0); - - // Check if any part of the month overlaps with the selected date range - return monthStart <= endDate && monthEnd >= startDate; - }; - - yearSelect.addEventListener('change', (event) => { - const selectedOption = event.target.selectedOptions[0]; - const selectedYear = selectedOption.value; - const availableMonths = JSON.parse(selectedOption.dataset.months || '[]'); - - // Update whole year link with selected year - const wholeYearLink = document.getElementById('whole-year-link'); - const startDate = `${selectedYear}-01-01T00:00`; - const endDate = `${selectedYear}-12-31T23:59`; - const href = `map?end_at=${encodeURIComponent(endDate)}&start_at=${encodeURIComponent(startDate)}`; - wholeYearLink.setAttribute('href', href); - - updateMonthLinks(selectedYear, availableMonths); - }); - - // If we have a current year, set it and update month links - if (currentYear && currentYearData) { - yearSelect.value = currentYear; - updateMonthLinks(currentYear, currentYearData.months); - } - }) - .catch(error => { - const yearSelect = document.getElementById('year-select'); - yearSelect.innerHTML = ''; - }); + this.fetchAndDisplayTrackedMonths(div, currentYear, currentMonth, allMonths); div.style.backgroundColor = 'white'; div.style.padding = '10px'; @@ -1137,7 +1031,7 @@ export default class extends Controller { div.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)'; div.style.marginRight = '10px'; div.style.marginTop = '10px'; - div.style.minWidth = '300px'; + div.style.width = '300px'; div.style.maxHeight = '80vh'; div.style.overflowY = 'auto'; @@ -1148,7 +1042,7 @@ export default class extends Controller {

Visited cities

+ style="max-height: 300px; overflow-y: auto; overflow-x: auto; padding-right: 10px;">

Loading visited places...

@@ -1161,19 +1055,144 @@ export default class extends Controller { // Fetch visited cities when panel is first created this.fetchAndDisplayVisitedCities(); + // Set initial display style based on localStorage + const isPanelOpen = localStorage.getItem('mapPanelOpen') === 'true'; + div.style.display = isPanelOpen ? 'block' : 'none'; + return div; }; - // Only add the panel if we should show it - const urlParams = new URLSearchParams(window.location.search); - const isPanelOpen = localStorage.getItem('mapPanelOpen') === 'true'; - const hasDateParams = urlParams.has('start_at') && urlParams.has('end_at'); + this.map.addControl(this.rightPanel); + } - if (isPanelOpen || hasDateParams) { - this.map.addControl(this.rightPanel); - localStorage.setItem('mapPanelOpen', 'true'); - } else { - localStorage.setItem('mapPanelOpen', 'false'); + async fetchAndDisplayTrackedMonths(div, currentYear, currentMonth, allMonths) { + try { + let yearsData; + + // Check cache first + if (this.trackedMonthsCache) { + yearsData = this.trackedMonthsCache; + } else { + const response = await fetch(`/api/v1/points/tracked_months?api_key=${this.apiKey}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + yearsData = await response.json(); + // Store in cache + this.trackedMonthsCache = yearsData; + } + + const yearSelect = document.getElementById('year-select'); + + if (!Array.isArray(yearsData) || yearsData.length === 0) { + yearSelect.innerHTML = ''; + return; + } + + // Check if the current year exists in the API response + const currentYearData = yearsData.find(yearData => yearData.year.toString() === currentYear); + + const options = yearsData + .filter(yearData => yearData && yearData.year) + .map(yearData => { + const months = Array.isArray(yearData.months) ? yearData.months : []; + const isCurrentYear = yearData.year.toString() === currentYear; + return ` + + `; + }) + .join(''); + + yearSelect.innerHTML = ` + + ${options} + `; + + const updateMonthLinks = (selectedYear, availableMonths) => { + // Get current date from URL parameters + const urlParams = new URLSearchParams(window.location.search); + const startDate = urlParams.get('start_at') ? new Date(urlParams.get('start_at')) : new Date(); + const endDate = urlParams.get('end_at') ? new Date(urlParams.get('end_at')) : new Date(); + + allMonths.forEach((month, index) => { + const monthLink = div.querySelector(`a[data-month-name="${month}"]`); + if (!monthLink) return; + + // Update the content to show the month name instead of loading dots + monthLink.innerHTML = month; + + // Check if this month falls within the selected date range + const isSelected = startDate && endDate && + selectedYear === startDate.getFullYear().toString() && // Only check months for the currently selected year + isMonthInRange(index, startDate, endDate, parseInt(selectedYear)); + + if (availableMonths.includes(month)) { + monthLink.classList.remove('disabled'); + monthLink.style.pointerEvents = 'auto'; + monthLink.style.opacity = '1'; + + // Update the active state based on selection + if (isSelected) { + monthLink.classList.add('btn-active', 'btn-primary'); + } else { + monthLink.classList.remove('btn-active', 'btn-primary'); + } + + const monthNum = (index + 1).toString().padStart(2, '0'); + const startDate = `${selectedYear}-${monthNum}-01T00:00`; + const lastDay = new Date(selectedYear, index + 1, 0).getDate(); + const endDate = `${selectedYear}-${monthNum}-${lastDay}T23:59`; + + const href = `map?end_at=${encodeURIComponent(endDate)}&start_at=${encodeURIComponent(startDate)}`; + monthLink.setAttribute('href', href); + } else { + monthLink.classList.add('disabled'); + monthLink.classList.remove('btn-active', 'btn-primary'); + monthLink.style.pointerEvents = 'none'; + monthLink.style.opacity = '0.6'; + monthLink.setAttribute('href', '#'); + } + }); + }; + + // Helper function to check if a month falls within a date range + const isMonthInRange = (monthIndex, startDate, endDate, selectedYear) => { + // Create date objects for the first and last day of the month in the selected year + const monthStart = new Date(selectedYear, monthIndex, 1); + const monthEnd = new Date(selectedYear, monthIndex + 1, 0); + + // Check if any part of the month overlaps with the selected date range + return monthStart <= endDate && monthEnd >= startDate; + }; + + yearSelect.addEventListener('change', (event) => { + const selectedOption = event.target.selectedOptions[0]; + const selectedYear = selectedOption.value; + const availableMonths = JSON.parse(selectedOption.dataset.months || '[]'); + + // Update whole year link with selected year + const wholeYearLink = document.getElementById('whole-year-link'); + const startDate = `${selectedYear}-01-01T00:00`; + const endDate = `${selectedYear}-12-31T23:59`; + const href = `map?end_at=${encodeURIComponent(endDate)}&start_at=${encodeURIComponent(startDate)}`; + wholeYearLink.setAttribute('href', href); + + updateMonthLinks(selectedYear, availableMonths); + }); + + // If we have a current year, set it and update month links + if (currentYear && currentYearData) { + yearSelect.value = currentYear; + updateMonthLinks(currentYear, currentYearData.months); + } + } catch (error) { + const yearSelect = document.getElementById('year-select'); + yearSelect.innerHTML = ''; + console.error('Error fetching tracked months:', error); } } @@ -1213,6 +1232,15 @@ export default class extends Controller { const startAt = urlParams.get('start_at') || new Date().toISOString(); const endAt = urlParams.get('end_at') || new Date().toISOString(); + // Create a cache key from the date range + const cacheKey = `${startAt}-${endAt}`; + + // Check if we have cached data for this date range + if (this.visitedCitiesCache.has(cacheKey)) { + this.displayVisitedCities(this.visitedCitiesCache.get(cacheKey)); + return; + } + try { const response = await fetch(`/api/v1/countries/visited_cities?api_key=${this.apiKey}&start_at=${startAt}&end_at=${endAt}`, { headers: { @@ -1226,6 +1254,10 @@ export default class extends Controller { } const data = await response.json(); + + // Cache the results + this.visitedCitiesCache.set(cacheKey, data.data); + this.displayVisitedCities(data.data); } catch (error) { console.error('Error fetching visited cities:', error); @@ -1246,11 +1278,11 @@ export default class extends Controller { } const html = citiesData.map(country => ` -
+

${country.country}