diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb
index 89043d5a..4dc37198 100644
--- a/app/controllers/stats_controller.rb
+++ b/app/controllers/stats_controller.rb
@@ -124,7 +124,44 @@ def index
# Get daily and all-time stats
@daily_stats = StatsService.daily_stats
- @all_time_stats = StatsService.all_time_daily_stats
+ full_series = StatsService.all_time_daily_stats
+
+ @range = params[:range].to_s
+ @range = "all" unless ["90", "365", "all"].include?(@range)
+
+ series_for_range = case @range
+ when "90"
+ full_series.last(90)
+ when "365"
+ full_series.last(365)
+ else
+ full_series
+ end
+
+ left_margin = 8
+ right_margin = 2
+ available_width = 100 - left_margin - right_margin
+ min_bar_width = 0.15
+ bar_spacing = 0.05
+ max_bars = (available_width / (min_bar_width + bar_spacing)).floor
+
+ if series_for_range.length <= max_bars
+ @chart_stats = series_for_range
+ else
+ bin_size = (series_for_range.length.to_f / max_bars).ceil
+ @chart_stats = []
+
+ series_for_range.each_slice(bin_size) do |bin|
+ if bin.length == 1
+ @chart_stats << bin.first
+ else
+ total_count = bin.sum(&:count)
+ last_date = bin.last.date
+ @chart_stats << OpenStruct.new(date: last_date, count: total_count)
+ end
+ end
+ end
+ @all_time_stats = full_series
# Get top posters for different time periods
@top_posters_today = StatsService.top_posters_today
diff --git a/app/views/stats/index.html.erb b/app/views/stats/index.html.erb
index a7f3b73d..96f2a51a 100644
--- a/app/views/stats/index.html.erb
+++ b/app/views/stats/index.html.erb
@@ -542,25 +542,30 @@
+
+ <%= form_with url: stats_path, method: :get, local: true do |f| %>
+
+
+ <% end %>
+
<%
- # Use all-time stats
- max_count = @all_time_stats.map(&:count).max
- total_days = @all_time_stats.length
+ max_count = @chart_stats.map(&:count).max
+ total_days = @chart_stats.length
- # Fixed dimensions
graph_height = 240
left_margin = 8
right_margin = 2
bottom_margin = 25
- # Calculate bar width and spacing
available_width = 100 - left_margin - right_margin
min_bar_width = 0.15
bar_spacing = 0.05
bar_width = [(available_width - (total_days * bar_spacing)) / total_days, min_bar_width].max
-
- # Show fewer labels for readability
label_interval = (total_days / 6.0).ceil
%>
@@ -632,7 +637,7 @@
/>
- <% @all_time_stats.each_with_index do |stat, index| %>
+ <% @chart_stats.each_with_index do |stat, index| %>
<%
bar_height = (stat.count.to_f / max_count * (graph_height - bottom_margin - 10)).round
x_pos = left_margin + (index * (bar_width + bar_spacing))
diff --git a/test/controllers/stats_controller_test.rb b/test/controllers/stats_controller_test.rb
new file mode 100644
index 00000000..f74858ee
--- /dev/null
+++ b/test/controllers/stats_controller_test.rb
@@ -0,0 +1,40 @@
+require "test_helper"
+
+class StatsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @user = users(:david)
+ post "/session", params: { email: @user.email_address, password: "secret123456" }
+ end
+
+ test "index with default range renders successfully" do
+ get "/stats"
+ assert_response :success
+ assert_select "select[name='range'] option[value='all'][selected]"
+ end
+
+ test "index with range=90 renders successfully" do
+ get "/stats", params: { range: "90" }
+ assert_response :success
+ assert_select "select[name='range'] option[value='90'][selected]"
+ end
+
+ test "index with range=365 renders successfully" do
+ get "/stats", params: { range: "365" }
+ assert_response :success
+ assert_select "select[name='range'] option[value='365'][selected]"
+ end
+
+ test "index with invalid range defaults to all" do
+ get "/stats", params: { range: "invalid" }
+ assert_response :success
+ assert_select "select[name='range'] option[value='all'][selected]"
+ end
+
+ test "chart_stats is set correctly for different ranges" do
+ get "/stats", params: { range: "90" }
+ assert_response :success
+ assert_not_nil assigns(:chart_stats)
+ assert_not_nil assigns(:range)
+ assert_equal "90", assigns(:range)
+ end
+end