import L from "leaflet"; import "leaflet.heat"; import { createAllMapLayers } from "../maps/layers"; import BaseController from "./base_controller"; export default class extends BaseController { static targets = ["map", "loading", "heatmapBtn", "pointsBtn"]; connect() { super.connect(); console.log("StatPage controller connected"); // Get data attributes from the element (will be passed from the view) this.year = parseInt(this.element.dataset.year || new Date().getFullYear()); this.month = parseInt(this.element.dataset.month || new Date().getMonth() + 1); this.apiKey = this.element.dataset.apiKey; this.selfHosted = this.element.dataset.selfHosted || this.selfHostedValue; console.log(`Loading data for ${this.month}/${this.year} with API key: ${this.apiKey ? 'present' : 'missing'}`); // Initialize map after a short delay to ensure container is ready setTimeout(() => { this.initializeMap(); }, 100); } disconnect() { if (this.map) { this.map.remove(); } console.log("StatPage controller disconnected"); } initializeMap() { if (!this.mapTarget) { console.error("Map target not found"); return; } try { // Initialize Leaflet map this.map = L.map(this.mapTarget, { zoomControl: true, scrollWheelZoom: true, doubleClickZoom: true, boxZoom: false, keyboard: false, dragging: true, touchZoom: true }).setView([52.520008, 13.404954], 10); // Default to Berlin // Add dynamic tile layer based on self-hosted setting this.addMapLayers(); // Add small scale control L.control.scale({ position: 'bottomright', maxWidth: 100, imperial: true, metric: true }).addTo(this.map); // Initialize layers this.markersLayer = L.layerGroup(); // Don't add to map initially this.heatmapLayer = null; // Load data for this month this.loadMonthData(); } catch (error) { console.error("Error initializing map:", error); this.showError("Failed to initialize map"); } } async loadMonthData() { try { // Show loading this.showLoading(true); // Calculate date range for the month const startDate = `${this.year}-${this.month.toString().padStart(2, '0')}-01T00:00:00`; const lastDay = new Date(this.year, this.month, 0).getDate(); const endDate = `${this.year}-${this.month.toString().padStart(2, '0')}-${lastDay}T23:59:59`; console.log(`Fetching points from ${startDate} to ${endDate}`); // Fetch points data for the month using Authorization header const response = await fetch(`/api/v1/points?start_at=${encodeURIComponent(startDate)}&end_at=${encodeURIComponent(endDate)}&per_page=1000`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` } }); if (!response.ok) { console.error(`API request failed with status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log(`Received ${Array.isArray(data) ? data.length : 0} points from API`); if (Array.isArray(data) && data.length > 0) { this.processPointsData(data); } else { console.log("No points data available for this month"); this.showNoData(); } } catch (error) { console.error("Error loading month data:", error); this.showError("Failed to load location data"); // Don't fallback to mock data - show the error instead } finally { this.showLoading(false); } } processPointsData(points) { console.log(`Processing ${points.length} points for ${this.month}/${this.year}`); // Clear existing markers this.markersLayer.clearLayers(); // Convert points to markers (API returns latitude/longitude as strings) const markers = points.map(point => { const lat = parseFloat(point.latitude); const lng = parseFloat(point.longitude); return L.circleMarker([lat, lng], { radius: 3, fillColor: '#570df8', color: '#570df8', weight: 1, opacity: 0.8, fillOpacity: 0.6 }); }); // Add markers to layer (but don't add to map yet) markers.forEach(marker => { this.markersLayer.addLayer(marker); }); // Prepare data for heatmap (convert strings to numbers) this.heatmapData = points.map(point => [ parseFloat(point.latitude), parseFloat(point.longitude), 0.5 ]); // Show heatmap by default if (this.heatmapData.length > 0) { this.heatmapLayer = L.heatLayer(this.heatmapData, { radius: 25, blur: 15, maxZoom: 17, max: 1.0 }).addTo(this.map); // Set button states this.heatmapBtnTarget.classList.add('btn-active'); this.pointsBtnTarget.classList.remove('btn-active'); } // Fit map to show all points if (points.length > 0) { const group = new L.featureGroup(markers); this.map.fitBounds(group.getBounds().pad(0.1)); } console.log("Points processed successfully"); } toggleHeatmap() { if (!this.heatmapData || this.heatmapData.length === 0) { console.warn("No heatmap data available"); return; } if (this.heatmapLayer && this.map.hasLayer(this.heatmapLayer)) { // Remove heatmap this.map.removeLayer(this.heatmapLayer); this.heatmapLayer = null; this.heatmapBtnTarget.classList.remove('btn-active'); // Show points if (!this.map.hasLayer(this.markersLayer)) { this.map.addLayer(this.markersLayer); this.pointsBtnTarget.classList.add('btn-active'); } } else { // Add heatmap this.heatmapLayer = L.heatLayer(this.heatmapData, { radius: 25, blur: 15, maxZoom: 17, max: 1.0 }).addTo(this.map); this.heatmapBtnTarget.classList.add('btn-active'); // Hide points if (this.map.hasLayer(this.markersLayer)) { this.map.removeLayer(this.markersLayer); this.pointsBtnTarget.classList.remove('btn-active'); } } } togglePoints() { if (this.map.hasLayer(this.markersLayer)) { // Remove points this.map.removeLayer(this.markersLayer); this.pointsBtnTarget.classList.remove('btn-active'); } else { // Add points this.map.addLayer(this.markersLayer); this.pointsBtnTarget.classList.add('btn-active'); // Remove heatmap if active if (this.heatmapLayer && this.map.hasLayer(this.heatmapLayer)) { this.map.removeLayer(this.heatmapLayer); this.heatmapBtnTarget.classList.remove('btn-active'); } } } showLoading(show) { if (this.hasLoadingTarget) { this.loadingTarget.style.display = show ? 'flex' : 'none'; } } showError(message) { console.error(message); if (this.hasLoadingTarget) { this.loadingTarget.innerHTML = `
${message}
`; this.loadingTarget.style.display = 'flex'; } } showNoData() { console.log("No data available for this month"); if (this.hasLoadingTarget) { this.loadingTarget.innerHTML = `
No location data available for ${new Date(this.year, this.month - 1).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
`; this.loadingTarget.style.display = 'flex'; } } addMapLayers() { try { // Use appropriate default layer based on self-hosted mode const selectedLayerName = this.selfHosted === "true" ? "OpenStreetMap" : "Light"; const maps = createAllMapLayers(this.map, selectedLayerName, this.selfHosted, 'dark'); // If no layers were created, fall back to OSM if (Object.keys(maps).length === 0) { console.warn('No map layers available, falling back to OSM'); this.addFallbackOSMLayer(); } } catch (error) { console.error('Error creating map layers:', error); console.log('Falling back to OSM tile layer'); this.addFallbackOSMLayer(); } } addFallbackOSMLayer() { L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors' }).addTo(this.map); } }