From 782aeb89af72ebb3bf6b4bc5a020d6f9e4c6c3af Mon Sep 17 00:00:00 2001
From: Eugene Burmakin
Date: Thu, 28 Nov 2024 15:29:17 +0100
Subject: [PATCH] Add distance calculation and trip cards to trips index
---
.app_version | 2 +-
app/controllers/trips_controller.rb | 6 +-
app/javascript/controllers/maps_controller.js | 82 -------------------
.../controllers/trip_map_controller.js | 60 ++++++++++++++
.../controllers/trips_controller.js | 2 +-
app/models/trip.rb | 22 ++++-
app/views/trips/_form.html.erb | 6 +-
app/views/trips/index.html.erb | 49 +++++------
app/views/trips/show.html.erb | 4 +-
db/migrate/20241127161621_create_trips.rb | 2 +-
db/schema.rb | 2 +-
11 files changed, 118 insertions(+), 119 deletions(-)
create mode 100644 app/javascript/controllers/trip_map_controller.js
diff --git a/.app_version b/.app_version
index c3d16c16..66333910 100644
--- a/.app_version
+++ b/.app_version
@@ -1 +1 @@
-0.17.2
+0.18.0
diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb
index c6e4b4a6..97492e74 100644
--- a/app/controllers/trips_controller.rb
+++ b/app/controllers/trips_controller.rb
@@ -6,7 +6,7 @@ class TripsController < ApplicationController
before_action :set_coordinates, only: %i[show edit]
def index
- @trips = current_user.trips.order(created_at: :desc).page(params[:page])
+ @trips = current_user.trips.order(started_at: :desc).page(params[:page]).per(6)
end
def show
@@ -22,6 +22,7 @@ class TripsController < ApplicationController
def new
@trip = Trip.new
+ @coordinates = []
end
def edit; end
@@ -55,7 +56,6 @@ class TripsController < ApplicationController
@trip = current_user.trips.find(params[:id])
end
-
def set_coordinates
@coordinates = @trip.points.pluck(
:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id,
@@ -64,6 +64,6 @@ class TripsController < ApplicationController
end
def trip_params
- params.require(:trip).permit(:name, :started_at, :ended_at, :notes, :field_notes)
+ params.require(:trip).permit(:name, :started_at, :ended_at, :notes)
end
end
diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index b3d62d8a..b00e6d16 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -788,88 +788,6 @@ export default class extends Controller {
this.layerControl = L.control.layers(this.baseMaps(), layerControl).addTo(this.map);
}
- // async fetchAndDisplayPhotos(startDate, endDate, retryCount = 0) {
- // const MAX_RETRIES = 3;
- // const RETRY_DELAY = 3000; // 3 seconds
-
- // // Create loading control
- // const LoadingControl = L.Control.extend({
- // onAdd: (map) => {
- // const container = L.DomUtil.create('div', 'leaflet-loading-control');
- // container.innerHTML = '';
- // return container;
- // }
- // });
-
- // const loadingControl = new LoadingControl({ position: 'topleft' });
- // this.map.addControl(loadingControl);
-
- // try {
- // const params = new URLSearchParams({
- // api_key: this.apiKey,
- // start_date: startDate,
- // end_date: endDate
- // });
-
- // const response = await fetch(`/api/v1/photos?${params}`);
- // if (!response.ok) {
- // throw new Error(`HTTP error! status: ${response.status}`);
- // }
-
- // const photos = await response.json();
- // this.photoMarkers.clearLayers();
-
- // // Create a promise for each photo to track when it's fully loaded
- // const photoLoadPromises = photos.map(photo => {
- // return new Promise((resolve) => {
- // const img = new Image();
- // const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}`;
-
- // img.onload = () => {
- // this.createPhotoMarker(photo);
- // resolve();
- // };
-
- // img.onerror = () => {
- // console.error(`Failed to load photo ${photo.id}`);
- // resolve(); // Resolve anyway to not block other photos
- // };
-
- // img.src = thumbnailUrl;
- // });
- // });
-
- // // Wait for all photos to be loaded and rendered
- // await Promise.all(photoLoadPromises);
-
- // if (!this.map.hasLayer(this.photoMarkers)) {
- // this.photoMarkers.addTo(this.map);
- // }
-
- // // Show checkmark for 1 second before removing
- // const loadingSpinner = document.querySelector('.loading-spinner');
- // loadingSpinner.classList.add('done');
-
- // await new Promise(resolve => setTimeout(resolve, 1000));
-
- // } catch (error) {
- // console.error('Error fetching photos:', error);
- // showFlashMessage('error', 'Failed to fetch photos');
-
- // if (retryCount < MAX_RETRIES) {
- // console.log(`Retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
- // setTimeout(() => {
- // this.fetchAndDisplayPhotos(startDate, endDate, retryCount + 1);
- // }, RETRY_DELAY);
- // } else {
- // showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
- // }
- // } finally {
- // // Remove loading control after the delay
- // this.map.removeControl(loadingControl);
- // }
- // }
-
createPhotoMarker(photo) {
if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return;
diff --git a/app/javascript/controllers/trip_map_controller.js b/app/javascript/controllers/trip_map_controller.js
new file mode 100644
index 00000000..b7df8943
--- /dev/null
+++ b/app/javascript/controllers/trip_map_controller.js
@@ -0,0 +1,60 @@
+import { Controller } from "@hotwired/stimulus"
+import L from "leaflet"
+
+export default class extends Controller {
+ static values = {
+ tripId: Number,
+ coordinates: Array,
+ apiKey: String,
+ userSettings: Object,
+ timezone: String,
+ distanceUnit: String
+ }
+
+ connect() {
+ setTimeout(() => {
+ this.initializeMap()
+ }, 100)
+ }
+
+ initializeMap() {
+ // Initialize map with basic configuration
+ this.map = L.map(this.element, {
+ zoomControl: false,
+ dragging: false,
+ scrollWheelZoom: false,
+ attributionControl: true // Disable default attribution control
+ })
+
+ // Add the tile layer
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 19,
+ attribution: "© OpenStreetMap"
+ }).addTo(this.map)
+
+ // If we have coordinates, show the route
+ if (this.hasCoordinatesValue && this.coordinatesValue.length > 0) {
+ this.showRoute()
+ }
+ }
+
+ showRoute() {
+ const points = this.coordinatesValue.map(coord => [coord[0], coord[1]])
+
+ const polyline = L.polyline(points, {
+ color: 'blue',
+ weight: 3,
+ opacity: 0.8
+ }).addTo(this.map)
+
+ this.map.fitBounds(polyline.getBounds(), {
+ padding: [20, 20]
+ })
+ }
+
+ disconnect() {
+ if (this.map) {
+ this.map.remove()
+ }
+ }
+}
diff --git a/app/javascript/controllers/trips_controller.js b/app/javascript/controllers/trips_controller.js
index 5bb15e8c..76ab87ba 100644
--- a/app/javascript/controllers/trips_controller.js
+++ b/app/javascript/controllers/trips_controller.js
@@ -142,7 +142,7 @@ export default class extends Controller {
const polyline = L.polyline(points, {
color: 'blue',
weight: 3,
- opacity: 0.6
+ opacity: 0.8
})
// Add to polylines layer instead of directly to map
this.polylinesLayer.addTo(this.map)
diff --git a/app/models/trip.rb b/app/models/trip.rb
index a08e82ee..44f202ab 100644
--- a/app/models/trip.rb
+++ b/app/models/trip.rb
@@ -1,14 +1,16 @@
# frozen_string_literal: true
class Trip < ApplicationRecord
- has_rich_text :field_notes
+ has_rich_text :notes
belongs_to :user
validates :name, :started_at, :ended_at, presence: true
+ before_save :calculate_distance
+
def points
- user.points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp)
+ user.tracked_points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp)
end
def countries
@@ -34,4 +36,20 @@ class Trip < ApplicationRecord
{ url: "/api/v1/photos/#{asset['id']}/thumbnail.jpg?api_key=#{user.api_key}" }
end
end
+
+ private
+
+ def calculate_distance
+ distance = 0
+
+ points.each_cons(2) do |point1, point2|
+ distance_between = Geocoder::Calculations.distance_between(
+ point1.to_coordinates, point2.to_coordinates, units: ::DISTANCE_UNIT
+ )
+
+ distance += distance_between
+ end
+
+ self.distance = distance.round
+ end
end
diff --git a/app/views/trips/_form.html.erb b/app/views/trips/_form.html.erb
index 221e2ff6..cf5518ff 100644
--- a/app/views/trips/_form.html.erb
+++ b/app/views/trips/_form.html.erb
@@ -20,7 +20,7 @@
data-distance_unit="<%= DISTANCE_UNIT %>"
data-api_key="<%= current_user.api_key %>"
data-user_settings="<%= current_user.settings.to_json %>"
- data-coordinates="<%= [].to_json %>"
+ data-coordinates="<%= @coordinates.to_json %>"
data-timezone="<%= Rails.configuration.time_zone %>">
@@ -61,8 +61,8 @@
- <%= form.label :field_notes %>
- <%= form.rich_text_area :field_notes %>
+ <%= form.label :notes %>
+ <%= form.rich_text_area :notes %>
diff --git a/app/views/trips/index.html.erb b/app/views/trips/index.html.erb
index 89a28184..bc1bec31 100644
--- a/app/views/trips/index.html.erb
+++ b/app/views/trips/index.html.erb
@@ -24,29 +24,32 @@
<%= paginate @trips %>
-
-
-
-
- | Name |
- Started at |
- Ended at |
-
-
-
- <% @trips.each do |trip| %>
-
- | <%= link_to trip.name, trip, class: 'underline hover:no-underline' %> |
- <%= trip.started_at.strftime("%d.%m.%Y, %H:%M") %> |
- <%= trip.ended_at.strftime("%d.%m.%Y, %H:%M") %> |
-
- <% end %>
-
-
+
+
+ <% @trips.each do |trip| %>
+
+
+
+ <%= link_to trip.name, trip, class: 'hover:underline' %>
+
+
+ <%= "#{human_date(trip.started_at)} – #{human_date(trip.ended_at)}, #{trip.distance} #{DISTANCE_UNIT}" %>
+
+
+
+
+
+
+ <% end %>
<% end %>
diff --git a/app/views/trips/show.html.erb b/app/views/trips/show.html.erb
index c1f2c25d..8339c7ad 100644
--- a/app/views/trips/show.html.erb
+++ b/app/views/trips/show.html.erb
@@ -9,7 +9,7 @@
<% if @trip.countries.any? %>
- <%= @trip.countries.join(', ') %>
+ <%= "#{@trip.countries.join(', ')} (#{@trip.distance} #{DISTANCE_UNIT})" %>
<% end %>
@@ -34,7 +34,7 @@
- <%= @trip.field_notes.body %>
+ <%= @trip.notes.body %>
diff --git a/db/migrate/20241127161621_create_trips.rb b/db/migrate/20241127161621_create_trips.rb
index 500b32bb..5bb787f6 100644
--- a/db/migrate/20241127161621_create_trips.rb
+++ b/db/migrate/20241127161621_create_trips.rb
@@ -6,7 +6,7 @@ class CreateTrips < ActiveRecord::Migration[7.2]
t.string :name, null: false
t.datetime :started_at, null: false
t.datetime :ended_at, null: false
- t.text :notes
+ t.integer :distance
t.references :user, null: false, foreign_key: true
t.timestamps
diff --git a/db/schema.rb b/db/schema.rb
index 6cdfac14..aa3daa37 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -189,7 +189,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
t.string "name", null: false
t.datetime "started_at", null: false
t.datetime "ended_at", null: false
- t.text "notes"
+ t.integer "distance"
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false