mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Add distance calculation and trip cards to trips index
This commit is contained in:
parent
4dca91b62d
commit
782aeb89af
11 changed files with 118 additions and 119 deletions
|
|
@ -1 +1 @@
|
||||||
0.17.2
|
0.18.0
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ class TripsController < ApplicationController
|
||||||
before_action :set_coordinates, only: %i[show edit]
|
before_action :set_coordinates, only: %i[show edit]
|
||||||
|
|
||||||
def index
|
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
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
@ -22,6 +22,7 @@ class TripsController < ApplicationController
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@trip = Trip.new
|
@trip = Trip.new
|
||||||
|
@coordinates = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit; end
|
||||||
|
|
@ -55,7 +56,6 @@ class TripsController < ApplicationController
|
||||||
@trip = current_user.trips.find(params[:id])
|
@trip = current_user.trips.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def set_coordinates
|
def set_coordinates
|
||||||
@coordinates = @trip.points.pluck(
|
@coordinates = @trip.points.pluck(
|
||||||
:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id,
|
:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id,
|
||||||
|
|
@ -64,6 +64,6 @@ class TripsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def trip_params
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -788,88 +788,6 @@ export default class extends Controller {
|
||||||
this.layerControl = L.control.layers(this.baseMaps(), layerControl).addTo(this.map);
|
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 = '<div class="loading-spinner"></div>';
|
|
||||||
// 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) {
|
createPhotoMarker(photo) {
|
||||||
if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return;
|
if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return;
|
||||||
|
|
||||||
|
|
|
||||||
60
app/javascript/controllers/trip_map_controller.js
Normal file
60
app/javascript/controllers/trip_map_controller.js
Normal file
|
|
@ -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: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
||||||
|
}).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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -142,7 +142,7 @@ export default class extends Controller {
|
||||||
const polyline = L.polyline(points, {
|
const polyline = L.polyline(points, {
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
weight: 3,
|
weight: 3,
|
||||||
opacity: 0.6
|
opacity: 0.8
|
||||||
})
|
})
|
||||||
// Add to polylines layer instead of directly to map
|
// Add to polylines layer instead of directly to map
|
||||||
this.polylinesLayer.addTo(this.map)
|
this.polylinesLayer.addTo(this.map)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Trip < ApplicationRecord
|
class Trip < ApplicationRecord
|
||||||
has_rich_text :field_notes
|
has_rich_text :notes
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
validates :name, :started_at, :ended_at, presence: true
|
validates :name, :started_at, :ended_at, presence: true
|
||||||
|
|
||||||
|
before_save :calculate_distance
|
||||||
|
|
||||||
def points
|
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
|
end
|
||||||
|
|
||||||
def countries
|
def countries
|
||||||
|
|
@ -34,4 +36,20 @@ class Trip < ApplicationRecord
|
||||||
{ url: "/api/v1/photos/#{asset['id']}/thumbnail.jpg?api_key=#{user.api_key}" }
|
{ url: "/api/v1/photos/#{asset['id']}/thumbnail.jpg?api_key=#{user.api_key}" }
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
data-distance_unit="<%= DISTANCE_UNIT %>"
|
data-distance_unit="<%= DISTANCE_UNIT %>"
|
||||||
data-api_key="<%= current_user.api_key %>"
|
data-api_key="<%= current_user.api_key %>"
|
||||||
data-user_settings="<%= current_user.settings.to_json %>"
|
data-user_settings="<%= current_user.settings.to_json %>"
|
||||||
data-coordinates="<%= [].to_json %>"
|
data-coordinates="<%= @coordinates.to_json %>"
|
||||||
data-timezone="<%= Rails.configuration.time_zone %>">
|
data-timezone="<%= Rails.configuration.time_zone %>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,8 +61,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= form.label :field_notes %>
|
<%= form.label :notes %>
|
||||||
<%= form.rich_text_area :field_notes %>
|
<%= form.rich_text_area :notes %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -24,29 +24,32 @@
|
||||||
<%= paginate @trips %>
|
<%= paginate @trips %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="table">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 my-4">
|
||||||
<thead>
|
<% @trips.each do |trip| %>
|
||||||
<tr>
|
<div class="card bg-base-200 shadow-xl" data-trip-id="<%= trip.id %>" id="trip-<%= trip.id %>">
|
||||||
<th>Name</th>
|
<div class="card-body">
|
||||||
<th>Started at</th>
|
<h2 class="card-title justify-center">
|
||||||
<th>Ended at</th>
|
<%= link_to trip.name, trip, class: 'hover:underline' %>
|
||||||
</tr>
|
</h2>
|
||||||
</thead>
|
<p class="text-sm text-gray-600 text-center">
|
||||||
<tbody
|
<%= "#{human_date(trip.started_at)} – #{human_date(trip.ended_at)}, #{trip.distance} #{DISTANCE_UNIT}" %>
|
||||||
data-controller="trips"
|
</p>
|
||||||
data-trips-target="index"
|
|
||||||
data-user-id="<%= current_user.id %>"
|
<div style="width: 100%; aspect-ratio: 1/1;"
|
||||||
>
|
id="map-<%= trip.id %>"
|
||||||
<% @trips.each do |trip| %>
|
class="rounded-lg"
|
||||||
<tr data-trip-id="<%= trip.id %>" id="trip-<%= trip.id %>">
|
data-controller="trip-map"
|
||||||
<td><%= link_to trip.name, trip, class: 'underline hover:no-underline' %></td>
|
data-trip-map-trip-id-value="<%= trip.id %>"
|
||||||
<td><%= trip.started_at.strftime("%d.%m.%Y, %H:%M") %></td>
|
data-trip-map-coordinates-value="<%= trip.points.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id, :country).to_json %>"
|
||||||
<td><%= trip.ended_at.strftime("%d.%m.%Y, %H:%M") %></td>
|
data-trip-map-api-key-value="<%= current_user.api_key %>"
|
||||||
</tr>
|
data-trip-map-user-settings-value="<%= current_user.settings.to_json %>"
|
||||||
<% end %>
|
data-trip-map-timezone-value="<%= Rails.configuration.time_zone %>"
|
||||||
</tbody>
|
data-trip-map-distance-unit-value="<%= DISTANCE_UNIT %>">
|
||||||
</table>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
</p>
|
</p>
|
||||||
<% if @trip.countries.any? %>
|
<% if @trip.countries.any? %>
|
||||||
<p class="text-lg text-base-content/60">
|
<p class="text-lg text-base-content/60">
|
||||||
<%= @trip.countries.join(', ') %>
|
<%= "#{@trip.countries.join(', ')} (#{@trip.distance} #{DISTANCE_UNIT})" %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div>
|
<div>
|
||||||
<%= @trip.field_notes.body %>
|
<%= @trip.notes.body %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Photos Grid Section -->
|
<!-- Photos Grid Section -->
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ class CreateTrips < ActiveRecord::Migration[7.2]
|
||||||
t.string :name, null: false
|
t.string :name, null: false
|
||||||
t.datetime :started_at, null: false
|
t.datetime :started_at, null: false
|
||||||
t.datetime :ended_at, null: false
|
t.datetime :ended_at, null: false
|
||||||
t.text :notes
|
t.integer :distance
|
||||||
t.references :user, null: false, foreign_key: true
|
t.references :user, null: false, foreign_key: true
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
|
|
|
||||||
2
db/schema.rb
generated
2
db/schema.rb
generated
|
|
@ -189,7 +189,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.datetime "started_at", null: false
|
t.datetime "started_at", null: false
|
||||||
t.datetime "ended_at", null: false
|
t.datetime "ended_at", null: false
|
||||||
t.text "notes"
|
t.integer "distance"
|
||||||
t.bigint "user_id", null: false
|
t.bigint "user_id", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue