From c39c26a4305aead5bb7c99c8968a61aed4d5d804 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Sat, 8 Mar 2025 20:31:12 +0100 Subject: [PATCH] Show points with dates in selected area --- app/javascript/maps/visits.js | 191 +++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 5 deletions(-) diff --git a/app/javascript/maps/visits.js b/app/javascript/maps/visits.js index 941b1a8b..92438bd7 100644 --- a/app/javascript/maps/visits.js +++ b/app/javascript/maps/visits.js @@ -15,6 +15,7 @@ export class VisitsManager { this.selectionMode = false; this.selectionRect = null; this.isSelectionActive = false; + this.selectedPoints = []; } /** @@ -80,6 +81,17 @@ export class VisitsManager { button.innerHTML = ''; button.title = 'Select Area'; button.id = 'selection-tool-button'; + button.style.width = '48px'; + button.style.height = '48px'; + button.style.border = 'none'; + button.style.cursor = 'pointer'; + button.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)'; + button.style.backgroundColor = 'white'; + button.style.borderRadius = '4px'; + button.style.padding = '0'; + button.style.lineHeight = '48px'; + button.style.fontSize = '18px'; + button.style.textAlign = 'center'; button.onclick = () => this.toggleSelectionMode(); return button; } @@ -169,7 +181,7 @@ export class VisitsManager { } /** - * Clears the current area selection + * Clears the selection rectangle and resets selection state */ clearSelection() { if (this.selectionRect) { @@ -178,6 +190,7 @@ export class VisitsManager { } this.isSelectionActive = false; this.startPoint = null; + this.selectedPoints = []; // If the drawer is open, refresh with time-based visits if (this.drawerOpen) { @@ -212,6 +225,13 @@ export class VisitsManager { } const visits = await response.json(); + + // Filter points in the selected area from DOM data + this.filterPointsInSelection(bounds); + + // Set selection as active to ensure date summary is displayed + this.isSelectionActive = true; + this.displayVisits(visits); // Make sure the drawer is open @@ -228,6 +248,155 @@ export class VisitsManager { } } + /** + * Filters points from DOM data that are within the selection bounds + * @param {L.LatLngBounds} bounds - The bounds of the selection rectangle + */ + filterPointsInSelection(bounds) { + if (!bounds) { + this.selectedPoints = []; + return; + } + + // Get points from the DOM + const allPoints = this.getPointsData(); + if (!allPoints || !allPoints.length) { + this.selectedPoints = []; + return; + } + + // Filter points that are within the bounds + this.selectedPoints = allPoints.filter(point => { + // Point format is expected to be [lat, lng, ...other data] + const lat = parseFloat(point[0]); + const lng = parseFloat(point[1]); + + if (isNaN(lat) || isNaN(lng)) return false; + + return bounds.contains([lat, lng]); + }); + } + + /** + * Gets points data from the DOM + * @returns {Array} Array of points with coordinates and timestamps + */ + getPointsData() { + const mapElement = document.getElementById('map'); + if (!mapElement) return []; + + // Get coordinates data from the data attribute + const coordinatesAttr = mapElement.getAttribute('data-coordinates'); + if (!coordinatesAttr) return []; + + try { + return JSON.parse(coordinatesAttr); + } catch (e) { + console.error('Error parsing coordinates data:', e); + return []; + } + } + + /** + * Groups visits by date + * @param {Array} visits - Array of visit objects + * @returns {Object} Object with dates as keys and counts as values + */ + groupVisitsByDate(visits) { + const dateGroups = {}; + + visits.forEach(visit => { + const startDate = new Date(visit.started_at); + const dateStr = startDate.toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + if (!dateGroups[dateStr]) { + dateGroups[dateStr] = { + count: 0, + points: 0, + date: startDate + }; + } + + dateGroups[dateStr].count++; + }); + + // If we have selected points, count them by date + if (this.selectedPoints && this.selectedPoints.length > 0) { + this.selectedPoints.forEach(point => { + // Point timestamp is at index 4 + const timestamp = point[4]; + if (!timestamp) return; + + // Convert timestamp to date string + const pointDate = new Date(parseInt(timestamp) * 1000); + const dateStr = pointDate.toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + if (!dateGroups[dateStr]) { + dateGroups[dateStr] = { + count: 0, + points: 0, + date: pointDate + }; + } + + dateGroups[dateStr].points++; + }); + } + + return dateGroups; + } + + /** + * Creates HTML for date summary panel + * @param {Object} dateGroups - Object with dates as keys and count/points values + * @returns {string} HTML string for date summary panel + */ + createDateSummaryHtml(dateGroups) { + // If there are no date groups, return empty string + if (Object.keys(dateGroups).length === 0) { + return ''; + } + + // Sort dates chronologically + const sortedDates = Object.keys(dateGroups).sort((a, b) => { + return dateGroups[a].date - dateGroups[b].date; + }); + + // Create HTML for each date group + const dateItems = sortedDates.map(dateStr => { + const pointsCount = dateGroups[dateStr].points || 0; + const visitsCount = dateGroups[dateStr].count || 0; + + return ` +
+
${dateStr}
+
+ ${pointsCount > 0 ? `
${pointsCount} points
` : ''} + ${visitsCount > 0 ? `
${visitsCount} visits
` : ''} +
+
+ `; + }).join(''); + + // Create the whole panel + return ` +
+

Data in Selected Area

+
+ ${dateItems} +
+
+ `; + } + /** * Adds a cancel button to the drawer to clear the selection */ @@ -266,7 +435,7 @@ export class VisitsManager { drawerButton.innerHTML = this.drawerOpen ? '➡️' : '⬅️'; } - const controls = document.querySelectorAll('.leaflet-control-layers, .toggle-panel-button, .leaflet-right-panel, .drawer-button'); + const controls = document.querySelectorAll('.leaflet-control-layers, .toggle-panel-button, .leaflet-right-panel, .drawer-button, #selection-tool-button'); controls.forEach(control => { control.classList.toggle('controls-shifted'); }); @@ -379,8 +548,19 @@ export class VisitsManager { const container = document.getElementById('visits-list'); if (!container) return; + // Group visits by date and count + const dateGroups = this.groupVisitsByDate(visits || []); + + // If we have points data and are in selection mode, calculate points per date + let dateGroupsHtml = ''; + if (this.isSelectionActive && this.selectionRect) { + // Create a date summary panel + dateGroupsHtml = this.createDateSummaryHtml(dateGroups); + } + if (!visits || visits.length === 0) { - container.innerHTML = '

No visits found in selected timeframe

'; + let noVisitsHtml = '

No visits found in selected timeframe

'; + container.innerHTML = dateGroupsHtml + noVisitsHtml; return; } @@ -420,7 +600,7 @@ export class VisitsManager { } }); - const html = visits + const visitsHtml = visits // Filter out declined visits .filter(visit => visit.status !== 'declined') .map(visit => { @@ -479,7 +659,8 @@ export class VisitsManager { `; }).join(''); - container.innerHTML = html; + // Combine date summary and visits HTML + container.innerHTML = dateGroupsHtml + visitsHtml; // Add the circles layer to the map this.visitCircles.addTo(this.map);