diff --git a/app/controllers/paper_trail_manager/changes_controller.rb b/app/controllers/paper_trail_manager/changes_controller.rb index 135fb0a..9422dbd 100644 --- a/app/controllers/paper_trail_manager/changes_controller.rb +++ b/app/controllers/paper_trail_manager/changes_controller.rb @@ -23,6 +23,12 @@ def index @versions = @versions.where(item_type: params[:type]) if params[:type] @versions = @versions.where(item_id: params[:id]) if params[:id] + # Date range filtering + @from = parse_date(params[:from]) + @to = parse_date(params[:to]) + @versions = @versions.where('created_at >= ?', @from.beginning_of_day) if @from + @versions = @versions.where('created_at <= ?', @to.end_of_day) if @to + # Ensure pagination parameters have sensible values @page = params[:page].to_i @page = nil if @page.zero? @@ -127,5 +133,14 @@ def change_revert_allowed?(version) PaperTrailManager.allow_revert?(self, version) end helper_method :change_revert_allowed? + + # Parse a date string, returning nil for invalid/missing values + def parse_date(value) + return nil if value.blank? + + Date.parse(value) + rescue Date::Error, ArgumentError + nil + end end end diff --git a/app/views/paper_trail_manager/changes/index.html.erb b/app/views/paper_trail_manager/changes/index.html.erb index e2aa4b8..0d66cb2 100644 --- a/app/views/paper_trail_manager/changes/index.html.erb +++ b/app/views/paper_trail_manager/changes/index.html.erb @@ -4,6 +4,19 @@ Show: <%= ([link_to('All', changes_path)] + change_item_types.map { |type| link_to(type.pluralize, changes_path(type: type)) }).join(' | ').html_safe %>

+ +<%= form_tag changes_path, method: :get, class: 'changes_date_filter' do %> + <%= hidden_field_tag :type, params[:type] if params[:type] %> + <%= hidden_field_tag :id, params[:id] if params[:id] %> + + <%= date_field_tag :from, params[:from], id: 'from' %> + + <%= date_field_tag :to, params[:to], id: 'to' %> + <%= submit_tag 'Filter', name: nil %> + <% if params[:from].present? || params[:to].present? %> + <%= link_to 'Clear', changes_path(type: params[:type], id: params[:id]), class: 'clear_filter' %> + <% end %> +<% end %>
diff --git a/spec/integration/date_filter_spec.rb b/spec/integration/date_filter_spec.rb new file mode 100644 index 0000000..3ff1883 --- /dev/null +++ b/spec/integration/date_filter_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PaperTrailManager, 'date range filtering', versioning: true do + let(:entity) { FactoryBot.create(:entity, name: 'Test Entity', status: 'Active') } + + before do + entity + entity.update(status: 'Updated') + end + + describe 'index with date filters' do + context 'with from parameter' do + it 'shows changes on or after the date' do + get changes_path(from: Date.today.to_s) + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_row') + end + + it 'returns no changes for future date' do + get changes_path(from: (Date.today + 1).to_s) + expect(response.body).to include('No changes found') + end + end + + context 'with to parameter' do + it 'shows changes on or before the date' do + get changes_path(to: Date.today.to_s) + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_row') + end + + it 'returns no changes for past date' do + get changes_path(to: (Date.today - 1).to_s) + expect(response.body).to include('No changes found') + end + end + + context 'with from and to combined' do + it 'shows changes within the range' do + get changes_path(from: Date.today.to_s, to: Date.today.to_s) + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_row') + end + end + + context 'combined with type filter' do + it 'respects both date and type filters' do + get changes_path(from: Date.today.to_s, type: 'Entity') + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_item', text: /Entity/) + end + end + + context 'with invalid date' do + it 'ignores invalid from date gracefully' do + get changes_path(from: 'not-a-date') + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_row') + end + + it 'ignores invalid to date gracefully' do + get changes_path(to: 'garbage') + expect(response).to have_http_status(:ok) + expect(response.body).to have_tag('.change_row') + end + end + + context 'date filter form' do + it 'renders date inputs' do + get changes_path + expect(response.body).to have_tag('input[type="date"][name="from"]') + expect(response.body).to have_tag('input[type="date"][name="to"]') + end + + it 'preserves date values in form' do + get changes_path(from: '2026-01-01', to: '2026-12-31') + expect(response.body).to have_tag('input[name="from"][value="2026-01-01"]') + expect(response.body).to have_tag('input[name="to"][value="2026-12-31"]') + end + end + end +end