Add nice charts to the stat cards

This commit is contained in:
Eugene Burmakin 2024-03-24 18:55:35 +01:00
parent 23805a6ef4
commit 29ac8c1136
15 changed files with 86 additions and 20 deletions

View file

@ -17,6 +17,7 @@ gem 'turbo-rails'
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
gem "importmap-rails"
gem "mapkick-rb"
gem "chartkick"
gem 'geocoder'
gem 'sidekiq'

View file

@ -83,6 +83,7 @@ GEM
msgpack (~> 1.2)
builder (3.2.4)
byebug (11.1.3)
chartkick (5.0.6)
coderay (1.1.3)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
@ -323,6 +324,7 @@ PLATFORMS
DEPENDENCIES
bootsnap
chartkick
debug
devise
dotenv-rails

View file

@ -18,7 +18,7 @@ class ImportsController < ApplicationController
files.each do |file|
import = current_user.imports.create(
name: file.original_filename,
source: params[:import][:source],
source: params[:import][:source]
)
import.file.attach(file)
@ -27,7 +27,7 @@ class ImportsController < ApplicationController
redirect_to imports_url, notice: "#{files.size} files are queued to be imported in background", status: :see_other
rescue StandardError => e
Import.where(user: current_user, name: files.map(&:original_filename)).destroy_all
Rails.logger.debug e.message
flash.now[:error] = e.message
redirect_to new_import_path, notice: e.message, status: :unprocessable_entity

View file

@ -6,12 +6,11 @@ class PointsController < ApplicationController
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
@points
.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id)
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7] }
@points.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id)
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7] }
@distance = distance
@start_at = Time.at(start_at)
@end_at = Time.at(end_at)
@start_at = Time.zone.at(start_at)
@end_at = Time.zone.at(end_at)
end
private
@ -23,7 +22,7 @@ class PointsController < ApplicationController
end
def end_at
return Date.today.end_of_day.to_i if params[:end_at].nil?
return Time.zone.today.end_of_day.to_i if params[:end_at].nil?
params[:end_at].to_datetime.to_i
end

View file

@ -22,4 +22,8 @@ module ApplicationHelper
{ start_at: start_at, end_at: end_at }
end
def header_colors
%w[info success warning error accent secondary primary]
end
end

View file

@ -7,3 +7,5 @@ import "@hotwired/turbo-rails"
import "mapkick/bundle"
import "leaflet"
import "leaflet-providers"
import "chartkick"
import "Chart.bundle"

View file

@ -1,5 +1,33 @@
# frozen_string_literal: true
class Stat < ApplicationRecord
validates :year, :month, presence: true
belongs_to :user
def timespan
DateTime.new(year, month).beginning_of_month..DateTime.new(year, month).end_of_month
end
def distance_by_day
timespan.to_a.map.with_index(1) do |day, index|
beginning_of_day = day.beginning_of_day.to_i
end_of_day = day.end_of_day.to_i
data = { day: index, distance: 0 }
# We have to filter by user as well
points = Point.where(timestamp: beginning_of_day..end_of_day)
points.each_cons(2) do |point1, point2|
distance = Geocoder::Calculations.distance_between(
[point1.latitude, point1.longitude], [point2.latitude, point2.longitude]
)
data[:distance] += distance
end
[data[:day], data[:distance].round(2)]
end
end
end

View file

@ -29,4 +29,8 @@ class User < ApplicationRecord
def total_cities
Stat.where(user: self).pluck(:toponyms).flatten.size
end
def total_reverse_geocoded
points.where.not(country: nil, city: nil).count
end
end

View file

@ -16,16 +16,13 @@ class CreateStats
end_of_month_timestamp = DateTime.new(year, month).end_of_month.to_i
points = points(beginning_of_month_timestamp, end_of_month_timestamp)
next if points.empty?
Stat.create(
year: year,
month: month,
distance: distance(points),
toponyms: toponyms(points),
user: user
)
stat = Stat.create(year:, month:, user:, distance: distance(points), toponyms: toponyms(points))
stat.update(daily_distance: stat.distance_by_day) if stat.persisted?
stat
end
end.compact
end

View file

@ -7,7 +7,7 @@
</h2>
<ul>
<% country[:cities].each do |city| %>
<li><%= city[:city] %> (<%= Time.at(city[:timestamp]).strftime("%d.%m.%Y") %>)</li>
<li><%= city[:city] %> (<%= Time.zone.at(city[:timestamp]).strftime("%d.%m.%Y") %>)</li>
<% end %>
</ul>
<% end %>

View file

@ -1,7 +1,7 @@
<div id="<%= dom_id stat %>" class="card w-full bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title">
<%= link_to points_url(month_timespan(stat)), class: 'underline hover:no-underline' do %>
<%= link_to points_url(month_timespan(stat)), class: "underline hover:no-underline text-#{header_colors.sample}" do %>
<%= "#{Date::MONTHNAMES[stat.month]} of #{stat.year}" %>
<% end %>
</h2>
@ -10,6 +10,13 @@
<div class="card-actions justify-end">
<%= stat.toponyms.count %> countries, <%= stat.toponyms.sum { _1['cities'].count } %> cities
</div>
<%= column_chart(
stat.daily_distance,
height: '100px',
suffix: ' km',
xtitle: 'Days',
ytitle: 'Distance'
) %>
<% end %>
</div>
</div>

View file

@ -7,10 +7,24 @@
<div class="stat-title">Total distance</div>
</div>
<div class="stat text-center">
<div class="stat-value text-success">
<%= number_with_delimiter current_user.points.count %>
</div>
<div class="stat-title">Geopoints tracked</div>
</div>
<% if REVERSE_GEOCODING_ENABLED %>
<div class="stat text-center">
<div class="stat-value text-secondary">
<%= current_user.total_countries %>
<%= current_user.total_reverse_geocoded %>
</div>
<div class="stat-title">Reverse geocoded points</div>
</div>
<div class="stat text-center">
<div class="stat-value text-warning">
<%= number_with_delimiter current_user.total_countries %>
</div>
<div class="stat-title">Countries visited</div>
</div>

View file

@ -12,3 +12,5 @@ pin_all_from "app/javascript/controllers", under: "controllers"
pin "mapkick/bundle", to: "mapkick.bundle.js"
pin "leaflet" # @1.9.4
pin "leaflet-providers" # @2.0.0
pin "chartkick", to: "chartkick.js"
pin "Chart.bundle", to: "Chart.bundle.js"

View file

@ -0,0 +1,5 @@
class AddDailyDistanceToStat < ActiveRecord::Migration[7.1]
def change
add_column :stats, :daily_distance, :jsonb, default: {}
end
end

3
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_03_24_161800) do
ActiveRecord::Schema[7.1].define(version: 2024_03_24_173315) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -101,6 +101,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_24_161800) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.jsonb "daily_distance", default: {}
t.index ["distance"], name: "index_stats_on_distance"
t.index ["month"], name: "index_stats_on_month"
t.index ["user_id"], name: "index_stats_on_user_id"