mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Update yearly digest layout and styles
This commit is contained in:
parent
c12709ac15
commit
da9e440cfa
13 changed files with 126 additions and 94 deletions
File diff suppressed because one or more lines are too long
1
app/assets/svg/icons/lucide/outline/calendar-plus-2.svg
Normal file
1
app/assets/svg/icons/lucide/outline/calendar-plus-2.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-plus2-icon lucide-calendar-plus-2"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M10 16h4"/><path d="M12 14v4"/></svg>
|
||||
|
After Width: | Height: | Size: 399 B |
|
|
@ -40,8 +40,7 @@ module Users
|
|||
end
|
||||
|
||||
def aggregate_toponyms
|
||||
countries = []
|
||||
cities = []
|
||||
country_cities = Hash.new { |h, k| h[k] = Set.new }
|
||||
|
||||
monthly_stats.each do |stat|
|
||||
toponyms = stat.toponyms
|
||||
|
|
@ -50,20 +49,27 @@ module Users
|
|||
toponyms.each do |toponym|
|
||||
next unless toponym.is_a?(Hash)
|
||||
|
||||
countries << toponym['country'] if toponym['country'].present?
|
||||
country = toponym['country']
|
||||
next unless country.present?
|
||||
|
||||
next unless toponym['cities'].is_a?(Array)
|
||||
|
||||
toponym['cities'].each do |city|
|
||||
cities << city['city'] if city.is_a?(Hash) && city['city'].present?
|
||||
if toponym['cities'].is_a?(Array)
|
||||
toponym['cities'].each do |city|
|
||||
city_name = city['city'] if city.is_a?(Hash)
|
||||
country_cities[country].add(city_name) if city_name.present?
|
||||
end
|
||||
else
|
||||
# Ensure country appears even if no cities
|
||||
country_cities[country]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
'countries' => countries.uniq.compact.sort,
|
||||
'cities' => cities.uniq.compact.sort
|
||||
}
|
||||
country_cities.sort_by { |country, _| country }.map do |country, cities|
|
||||
{
|
||||
'country' => country,
|
||||
'cities' => cities.to_a.sort.map { |city| { 'city' => city } }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def build_monthly_distances
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ module Users
|
|||
toponyms = stat.toponyms
|
||||
next [] unless toponyms.is_a?(Array)
|
||||
|
||||
toponyms.filter_map { |t| t['country'] if t.is_a?(Hash) }
|
||||
end.uniq.compact
|
||||
toponyms.filter_map { |t| t['country'] if t.is_a?(Hash) && t['country'].present? }
|
||||
end.uniq
|
||||
end
|
||||
|
||||
def extract_cities(stats)
|
||||
|
|
@ -68,9 +68,9 @@ module Users
|
|||
toponyms.flat_map do |t|
|
||||
next [] unless t.is_a?(Hash) && t['cities'].is_a?(Array)
|
||||
|
||||
t['cities'].filter_map { |c| c['city'] if c.is_a?(Hash) }
|
||||
t['cities'].filter_map { |c| c['city'] if c.is_a?(Hash) && c['city'].present? }
|
||||
end
|
||||
end.uniq.compact
|
||||
end.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ module Users
|
|||
toponyms = stat.toponyms
|
||||
next [] unless toponyms.is_a?(Array)
|
||||
|
||||
toponyms.filter_map { |t| t['country'] if t.is_a?(Hash) }
|
||||
end.uniq.compact.count
|
||||
toponyms.filter_map { |t| t['country'] if t.is_a?(Hash) && t['country'].present? }
|
||||
end.uniq.count
|
||||
end
|
||||
|
||||
def count_cities(stats)
|
||||
|
|
@ -70,9 +70,9 @@ module Users
|
|||
toponyms.flat_map do |t|
|
||||
next [] unless t.is_a?(Hash) && t['cities'].is_a?(Array)
|
||||
|
||||
t['cities'].filter_map { |c| c['city'] if c.is_a?(Hash) }
|
||||
t['cities'].filter_map { |c| c['city'] if c.is_a?(Hash) && c['city'].present? }
|
||||
end
|
||||
end.uniq.compact.count
|
||||
end.uniq.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<% content_for :title, 'Year-End Digests' %>
|
||||
|
||||
<div class="w-full my-5">
|
||||
<div class="max-w-xl mx-auto my-5">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold flex items-center gap-2">
|
||||
<%= icon 'earth' %> Year-End Digests
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<% if @available_years.any? && current_user.active? %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="btn btn-primary">
|
||||
<%= icon 'plus' %> Generate Digest
|
||||
<%= icon 'calendar-plus-2' %> Generate Digest
|
||||
</label>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<% @available_years.each do |year| %>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-xl mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="hero bg-gradient-to-br from-blue-600 to-purple-700 text-white rounded-lg shadow-lg mb-8">
|
||||
<div class="hero-content text-center py-12">
|
||||
|
|
@ -20,24 +20,24 @@
|
|||
<div class="stat place-items-center text-center">
|
||||
<div class="stat-title">Countries visited</div>
|
||||
<div class="stat-value text-secondary"><%= @digest.countries_count %></div>
|
||||
<% if @digest.first_time_countries.any? %>
|
||||
<div class="stat-desc text-success"><%= @digest.first_time_countries.count %> first time</div>
|
||||
<% end %>
|
||||
<div class="stat-desc <%= @digest.first_time_countries.any? ? 'text-success' : 'invisible' %>">
|
||||
<%= @digest.first_time_countries.any? ? "#{@digest.first_time_countries.count} first time" : '0 first time' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat place-items-center text-center">
|
||||
<div class="stat-title">Cities explored</div>
|
||||
<div class="stat-value text-accent"><%= @digest.cities_count %></div>
|
||||
<% if @digest.first_time_cities.any? %>
|
||||
<div class="stat-desc text-success"><%= @digest.first_time_cities.count %> first time</div>
|
||||
<% end %>
|
||||
<div class="stat-desc <%= @digest.first_time_cities.any? ? 'text-success' : 'invisible' %>">
|
||||
<%= @digest.first_time_cities.any? ? "#{@digest.first_time_cities.count} first time" : '0 first time' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- First Time Visits -->
|
||||
<% if @digest.first_time_countries.any? || @digest.first_time_cities.any? %>
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'star' %> First Time Visits
|
||||
</h2>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
<% if @digest.first_time_countries.any? %>
|
||||
<div class="mb-4">
|
||||
<h3 class="font-semibold mb-2">New Countries</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-2 justify-center">
|
||||
<% @digest.first_time_countries.each do |country| %>
|
||||
<span class="badge badge-success badge-lg"><%= country %></span>
|
||||
<% end %>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
<% if @digest.first_time_cities.any? %>
|
||||
<div>
|
||||
<h3 class="font-semibold mb-2">New Cities</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-2 justify-center">
|
||||
<% @digest.first_time_cities.take(5).each do |city| %>
|
||||
<span class="badge badge-outline"><%= city %></span>
|
||||
<% end %>
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<!-- Monthly Distance Chart -->
|
||||
<% if @digest.monthly_distances.present? %>
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'activity' %> Year by Month
|
||||
</h2>
|
||||
|
|
@ -101,11 +101,11 @@
|
|||
<!-- Top Countries by Time Spent -->
|
||||
<% if @digest.top_countries_by_time.any? %>
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'map-pin' %> Where They Spent the Most Time
|
||||
</h2>
|
||||
<ul class="space-y-2">
|
||||
<ul class="space-y-2 w-full">
|
||||
<% @digest.top_countries_by_time.take(3).each do |country| %>
|
||||
<li class="flex justify-between items-center p-3 bg-base-200 rounded-lg">
|
||||
<span class="font-semibold"><%= country['name'] %></span>
|
||||
|
|
@ -119,11 +119,11 @@
|
|||
|
||||
<!-- Countries & Cities -->
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'earth' %> Countries & Cities
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-4 w-full">
|
||||
<% @digest.toponyms&.each_with_index do |country, index| %>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-2 justify-center w-full">
|
||||
<span class="text-sm font-medium">Cities visited:</span>
|
||||
<% @digest.toponyms&.each do |country| %>
|
||||
<% country['cities']&.take(5)&.each do |city| %>
|
||||
|
|
@ -153,23 +153,23 @@
|
|||
|
||||
<!-- All-Time Stats -->
|
||||
<div class="card bg-slate-800 text-white shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title text-white">
|
||||
<%= icon 'trophy' %> All-Time Stats
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||||
<div class="stat">
|
||||
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||
<div class="stat place-items-center">
|
||||
<div class="stat-title text-gray-400">Countries visited</div>
|
||||
<div class="stat-value text-white"><%= @digest.total_countries_all_time %></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat place-items-center">
|
||||
<div class="stat-title text-gray-400">Cities explored</div>
|
||||
<div class="stat-value text-white"><%= @digest.total_cities_all_time %></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title text-gray-400">Total distance</div>
|
||||
<div class="stat-value text-white"><%= distance_with_unit(@digest.total_distance_all_time, @distance_unit) %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat place-items-center mt-2">
|
||||
<div class="stat-title text-gray-400">Total distance</div>
|
||||
<div class="stat-value text-white"><%= distance_with_unit(@digest.total_distance_all_time, @distance_unit) %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<% content_for :title, "#{@digest.year} Year in Review" %>
|
||||
|
||||
<div class="w-full my-5">
|
||||
<div class="max-w-xl mx-auto my-5">
|
||||
<!-- Header -->
|
||||
<div class="hero bg-gradient-to-br from-blue-600 to-purple-700 text-white rounded-lg shadow-lg mb-8">
|
||||
<div class="hero-content text-center py-12 relative w-full">
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<!-- Distance Card -->
|
||||
<div class="card bg-base-200 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<div class="stat-title flex items-center gap-2">
|
||||
<%= icon 'map' %> Distance Traveled
|
||||
</div>
|
||||
|
|
@ -40,11 +40,9 @@
|
|||
<%= icon 'globe' %> Countries
|
||||
</div>
|
||||
<div class="stat-value text-secondary"><%= @digest.countries_count %></div>
|
||||
<% if @digest.first_time_countries.any? %>
|
||||
<div class="stat-desc text-success font-medium">
|
||||
<%= icon 'star' %> <%= @digest.first_time_countries.count %> first time
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="stat-desc font-medium flex items-center gap-1 <%= @digest.first_time_countries.any? ? 'text-success' : 'invisible' %>">
|
||||
<%= icon 'star' %> <%= @digest.first_time_countries.any? ? "#{@digest.first_time_countries.count} first time" : '0 first time' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat place-items-center">
|
||||
|
|
@ -52,18 +50,16 @@
|
|||
<%= icon 'building' %> Cities
|
||||
</div>
|
||||
<div class="stat-value text-accent"><%= @digest.cities_count %></div>
|
||||
<% if @digest.first_time_cities.any? %>
|
||||
<div class="stat-desc text-success font-medium">
|
||||
<%= icon 'star' %> <%= @digest.first_time_cities.count %> first time
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="stat-desc font-medium flex items-center gap-1 <%= @digest.first_time_cities.any? ? 'text-success' : 'invisible' %>">
|
||||
<%= icon 'star' %> <%= @digest.first_time_cities.any? ? "#{@digest.first_time_cities.count} first time" : '0 first time' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- First Time Visits -->
|
||||
<% if @digest.first_time_countries.any? || @digest.first_time_cities.any? %>
|
||||
<div class="card bg-base-200 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'star' %> First Time Visits
|
||||
</h2>
|
||||
|
|
@ -71,7 +67,7 @@
|
|||
<% if @digest.first_time_countries.any? %>
|
||||
<div class="mb-4">
|
||||
<h3 class="font-semibold mb-2">New Countries</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-2 justify-center">
|
||||
<% @digest.first_time_countries.each do |country| %>
|
||||
<span class="badge badge-success badge-lg"><%= country %></span>
|
||||
<% end %>
|
||||
|
|
@ -82,7 +78,7 @@
|
|||
<% if @digest.first_time_cities.any? %>
|
||||
<div>
|
||||
<h3 class="font-semibold mb-2">New Cities</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex flex-wrap gap-2 justify-center">
|
||||
<% @digest.first_time_cities.take(10).each do |city| %>
|
||||
<span class="badge badge-outline"><%= city %></span>
|
||||
<% end %>
|
||||
|
|
@ -99,7 +95,7 @@
|
|||
<!-- Monthly Distance Chart -->
|
||||
<% if @digest.monthly_distances.present? %>
|
||||
<div class="card bg-base-200 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'activity' %> Your Year, Month by Month
|
||||
</h2>
|
||||
|
|
@ -127,11 +123,11 @@
|
|||
<!-- Top Countries by Time Spent -->
|
||||
<% if @digest.top_countries_by_time.any? %>
|
||||
<div class="card bg-base-200 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'map-pin' %> Where You Spent the Most Time
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-4 w-full">
|
||||
<% @digest.top_countries_by_time.take(5).each_with_index do |country, index| %>
|
||||
<div class="flex justify-between items-center p-3 bg-base-100 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
|
|
@ -150,11 +146,11 @@
|
|||
|
||||
<!-- All Countries & Cities -->
|
||||
<div class="card bg-base-200 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title">
|
||||
<%= icon 'earth' %> Countries & Cities
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-4 w-full">
|
||||
<% if @digest.toponyms.present? %>
|
||||
<% max_cities = @digest.toponyms.map { |country| country['cities']&.length || 0 }.max %>
|
||||
<% progress_colors = ['progress-primary', 'progress-secondary', 'progress-accent', 'progress-info', 'progress-success', 'progress-warning'] %>
|
||||
|
|
@ -183,23 +179,23 @@
|
|||
|
||||
<!-- All-Time Stats Footer -->
|
||||
<div class="card bg-slate-800 text-white shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<div class="card-body text-center items-center">
|
||||
<h2 class="card-title text-white">
|
||||
<%= icon 'trophy' %> All-Time Stats
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
|
||||
<div class="stat">
|
||||
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||
<div class="stat place-items-center">
|
||||
<div class="stat-title text-gray-400">Countries visited</div>
|
||||
<div class="stat-value text-white"><%= @digest.total_countries_all_time %></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat place-items-center">
|
||||
<div class="stat-title text-gray-400">Cities explored</div>
|
||||
<div class="stat-value text-white"><%= @digest.total_cities_all_time %></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title text-gray-400">Total distance</div>
|
||||
<div class="stat-value text-white"><%= distance_with_unit(@digest.total_distance_all_time, @distance_unit) %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat place-items-center mt-2">
|
||||
<div class="stat-title text-gray-400">Total distance</div>
|
||||
<div class="stat-value text-white"><%= distance_with_unit(@digest.total_distance_all_time, @distance_unit) %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
padding: 20px;
|
||||
margin: 16px 0;
|
||||
border: 1px solid #e2e8f0;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 36px;
|
||||
|
|
@ -113,6 +114,7 @@
|
|||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.all-time-footer h3 {
|
||||
color: white;
|
||||
|
|
@ -120,8 +122,6 @@
|
|||
font-size: 18px;
|
||||
}
|
||||
.all-time-stat {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
|
@ -130,9 +130,13 @@
|
|||
}
|
||||
.all-time-stat .label {
|
||||
opacity: 0.8;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.all-time-stat .value {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
|
|
@ -229,15 +233,19 @@
|
|||
<!-- All-Time Stats Footer -->
|
||||
<div class="all-time-footer">
|
||||
<h3>All-Time Stats</h3>
|
||||
<div class="all-time-stat">
|
||||
<span class="label">Countries visited</span>
|
||||
<span class="value"><%= @digest.total_countries_all_time %></span>
|
||||
</div>
|
||||
<div class="all-time-stat">
|
||||
<span class="label">Cities explored</span>
|
||||
<span class="value"><%= @digest.total_cities_all_time %></span>
|
||||
</div>
|
||||
<div class="all-time-stat">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom: 16px;">
|
||||
<tr>
|
||||
<td width="50%" style="text-align: center; padding: 8px;">
|
||||
<div class="label" style="opacity: 0.8; font-size: 12px; margin-bottom: 4px;">Countries visited</div>
|
||||
<div class="value" style="font-weight: 600; font-size: 24px;"><%= @digest.total_countries_all_time %></div>
|
||||
</td>
|
||||
<td width="50%" style="text-align: center; padding: 8px;">
|
||||
<div class="label" style="opacity: 0.8; font-size: 12px; margin-bottom: 4px;">Cities explored</div>
|
||||
<div class="value" style="font-weight: 600; font-size: 24px;"><%= @digest.total_cities_all_time %></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="all-time-stat" style="border-top: 1px solid rgba(255,255,255,0.1); padding-top: 16px;">
|
||||
<span class="label">Total distance</span>
|
||||
<span class="value"><%= distance_with_unit(@digest.total_distance_all_time, @distance_unit) %></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class CreateDigests < ActiveRecord::Migration[8.0]
|
|||
t.integer :period_type, null: false, default: 0 # enum: monthly: 0, yearly: 1
|
||||
|
||||
# Aggregated data
|
||||
t.integer :distance, null: false, default: 0 # Total distance in meters
|
||||
t.bigint :distance, null: false, default: 0 # Total distance in meters
|
||||
t.jsonb :toponyms, default: {} # Countries/cities data
|
||||
t.jsonb :monthly_distances, default: {} # {1: meters, 2: meters, ...}
|
||||
t.jsonb :time_spent_by_location, default: {} # Top locations by time
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeDigestsDistanceToBigint < ActiveRecord::Migration[8.0]
|
||||
# Safe: digests table is new with minimal data
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
if respond_to?(:safety_assured)
|
||||
safety_assured do
|
||||
change_column :digests, :distance, :bigint, null: false, default: 0
|
||||
end
|
||||
else
|
||||
change_column :digests, :distance, :bigint, null: false, default: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
4
db/schema.rb
generated
4
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_27_000001) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_27_193242) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
enable_extension "postgis"
|
||||
|
|
@ -84,7 +84,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_27_000001) do
|
|||
t.bigint "user_id", null: false
|
||||
t.integer "year", null: false
|
||||
t.integer "period_type", default: 0, null: false
|
||||
t.integer "distance", default: 0, null: false
|
||||
t.bigint "distance", default: 0, null: false
|
||||
t.jsonb "toponyms", default: {}
|
||||
t.jsonb "monthly_distances", default: {}
|
||||
t.jsonb "time_spent_by_location", default: {}
|
||||
|
|
|
|||
|
|
@ -57,12 +57,17 @@ RSpec.describe Users::Digests::CalculateYear do
|
|||
expect(calculate_digest.distance).to eq(125_000)
|
||||
end
|
||||
|
||||
it 'aggregates countries' do
|
||||
expect(calculate_digest.toponyms['countries']).to contain_exactly('France', 'Germany')
|
||||
end
|
||||
it 'aggregates countries with their cities' do
|
||||
toponyms = calculate_digest.toponyms
|
||||
|
||||
it 'aggregates cities' do
|
||||
expect(calculate_digest.toponyms['cities']).to contain_exactly('Berlin', 'Munich', 'Paris')
|
||||
countries = toponyms.map { |t| t['country'] }
|
||||
expect(countries).to contain_exactly('France', 'Germany')
|
||||
|
||||
germany = toponyms.find { |t| t['country'] == 'Germany' }
|
||||
expect(germany['cities'].map { |c| c['city'] }).to contain_exactly('Berlin', 'Munich')
|
||||
|
||||
france = toponyms.find { |t| t['country'] == 'France' }
|
||||
expect(france['cities'].map { |c| c['city'] }).to contain_exactly('Paris')
|
||||
end
|
||||
|
||||
it 'builds monthly distances' do
|
||||
|
|
|
|||
Loading…
Reference in a new issue