diff --git a/app/controllers/api/v1/countries/visited_cities_controller.rb b/app/controllers/api/v1/countries/visited_cities_controller.rb
new file mode 100644
index 00000000..2b79ffd7
--- /dev/null
+++ b/app/controllers/api/v1/countries/visited_cities_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Api::V1::Countries::VisitedCitiesController < ApiController
+ before_action :validate_params
+
+ def index
+ start_at = DateTime.parse(params[:start_at]).to_i
+ end_at = DateTime.parse(params[:end_at]).to_i
+
+ points = current_api_user
+ .tracked_points
+ .where(timestamp: start_at..end_at)
+
+ render json: { data: CountriesAndCities.new(points).call }
+ end
+
+ private
+
+ def validate_params
+ missing_params = %i[start_at end_at].select { |param| params[param].blank? }
+
+ if missing_params.any?
+ render json: {
+ error: "Missing required parameters: #{missing_params.join(', ')}"
+ }, status: :bad_request and return
+ end
+
+ params.permit(:start_at, :end_at)
+ end
+end
diff --git a/app/helpers/api/v1/points/tracked_months_helper.rb b/app/helpers/api/v1/points/tracked_months_helper.rb
deleted file mode 100644
index 7be4d70a..00000000
--- a/app/helpers/api/v1/points/tracked_months_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module Api::V1::Points::TrackedMonthsHelper
-end
diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index d4ed8c3a..9170f534 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -958,8 +958,9 @@ export default class extends Controller {
} else {
this.map.addControl(this.rightPanel);
localStorage.setItem('mapPanelOpen', 'true');
+ // Fetch visited cities when panel is opened
+ this.fetchAndDisplayVisitedCities();
}
-
return;
}
@@ -1008,6 +1009,7 @@ export default class extends Controller {
`).join('')}
+
`;
@@ -1141,6 +1143,24 @@ export default class extends Controller {
L.DomEvent.disableClickPropagation(div);
+ // Add container for visited cities
+ div.innerHTML += `
+
+
Visited cities
+
+
Loading visited places...
+
+
+ `;
+
+ // Prevent map zoom when scrolling the cities list
+ const citiesList = div.querySelector('#visited-cities-list');
+ L.DomEvent.disableScrollPropagation(citiesList);
+
+ // Fetch visited cities when panel is first created
+ this.fetchAndDisplayVisitedCities();
+
return div;
};
@@ -1187,5 +1207,71 @@ export default class extends Controller {
const endDate = `${year}-12-31T23:59`;
return `map?end_at=${encodeURIComponent(endDate)}&start_at=${encodeURIComponent(startDate)}`;
}
+
+ async fetchAndDisplayVisitedCities() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const startAt = urlParams.get('start_at') || new Date().toISOString();
+ const endAt = urlParams.get('end_at') || new Date().toISOString();
+
+ try {
+ const response = await fetch(`/api/v1/countries/visited_cities?api_key=${this.apiKey}&start_at=${startAt}&end_at=${endAt}`, {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+
+ const data = await response.json();
+ this.displayVisitedCities(data.data);
+ } catch (error) {
+ console.error('Error fetching visited cities:', error);
+ const container = document.getElementById('visited-cities-list');
+ if (container) {
+ container.innerHTML = 'Error loading visited places
';
+ }
+ }
+ }
+
+ displayVisitedCities(citiesData) {
+ const container = document.getElementById('visited-cities-list');
+ if (!container) return;
+
+ if (!citiesData || citiesData.length === 0) {
+ container.innerHTML = 'No places visited during this period
';
+ return;
+ }
+
+ const html = citiesData.map(country => `
+
+
${country.country}
+
+ ${country.cities.map(city => `
+ -
+ ${city.city}
+
+ (${new Date(city.timestamp * 1000).toLocaleDateString()})
+
+
+ `).join('')}
+
+
+ `).join('');
+
+ container.innerHTML = html;
+ }
+
+ formatDuration(seconds) {
+ const days = Math.floor(seconds / (24 * 60 * 60));
+ const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60));
+
+ if (days > 0) {
+ return `${days}d ${hours}h`;
+ }
+ return `${hours}h`;
+ }
}
diff --git a/config/routes.rb b/config/routes.rb
index 8f179cfa..ebad44e5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -78,6 +78,7 @@ Rails.application.routes.draw do
namespace :countries do
resources :borders, only: :index
+ resources :visited_cities, only: :index
end
namespace :points do
diff --git a/spec/requests/api/v1/countries/visited_cities_spec.rb b/spec/requests/api/v1/countries/visited_cities_spec.rb
new file mode 100644
index 00000000..88441b3a
--- /dev/null
+++ b/spec/requests/api/v1/countries/visited_cities_spec.rb
@@ -0,0 +1,7 @@
+require 'rails_helper'
+
+RSpec.describe "Api::V1::Countries::VisitedCities", type: :request do
+ describe "GET /index" do
+ pending "add some examples (or delete) #{__FILE__}"
+ end
+end