From 9beaaa08515a5048ae3dfe939c6d8d4705d83868 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 23 Nov 2024 01:45:10 +0200 Subject: [PATCH] adjust filters --- app/controllers/application_controller.rb | 17 ---- .../submissions_archived_controller.rb | 9 +- .../submissions_dashboard_controller.rb | 9 +- .../submissions_filters_controller.rb | 17 ++++ ...mplates_archived_submissions_controller.rb | 9 +- app/controllers/templates_controller.rb | 44 ++------- app/javascript/elements/set_date_button.js | 17 ++-- app/models/submission.rb | 7 +- app/views/shared/_search_input.html.erb | 5 + app/views/submissions_archived/index.html.erb | 23 +++-- .../submissions_dashboard/index.html.erb | 55 ++++++----- .../_applied_filters.html.erb | 50 ++++++++++ .../_date_buttons.html.erb | 39 ++++++++ .../_filter_button.html.erb | 27 ++++++ .../_filter_modal.html.erb | 18 ++++ app/views/submissions_filters/author.html.erb | 7 ++ .../submissions_filters/completed_at.html.erb | 15 +++ .../submissions_filters/created_at.html.erb | 15 +++ app/views/templates/filter.html.erb | 94 ------------------ app/views/templates/show.html.erb | 95 +++---------------- .../index.html.erb | 34 ++++--- config/locales/i18n.yml | 32 ++++--- config/routes.rb | 2 +- lib/submissions.rb | 2 +- lib/submissions/filter.rb | 64 +++++++++++++ lib/submissions/generate_audit_trail.rb | 2 +- lib/time_utils.rb | 2 + spec/system/template_spec.rb | 4 +- 28 files changed, 410 insertions(+), 304 deletions(-) create mode 100644 app/controllers/submissions_filters_controller.rb create mode 100644 app/views/submissions_filters/_applied_filters.html.erb create mode 100644 app/views/submissions_filters/_date_buttons.html.erb create mode 100644 app/views/submissions_filters/_filter_button.html.erb create mode 100644 app/views/submissions_filters/_filter_modal.html.erb create mode 100644 app/views/submissions_filters/author.html.erb create mode 100644 app/views/submissions_filters/completed_at.html.erb create mode 100644 app/views/submissions_filters/created_at.html.erb delete mode 100644 app/views/templates/filter.html.erb create mode 100644 lib/submissions/filter.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dd463d4a..a48146ee 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -115,21 +115,4 @@ class ApplicationController < ActionController::Base redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently end - - def date_range(key) - p = params.permit(key => %i[from to]).fetch(key, {}) - timezone = ActiveSupport::TimeZone[current_account.timezone] || Time.zone - start_date = timezone.parse(p[:from]) if p[:from].present? - end_date = timezone.parse(p[:to]) if p[:to].present? - - if start_date.present? && end_date.present? && start_date < end_date - start_date..end_date - elsif start_date.present? && end_date.present? && start_date == end_date - start_date.beginning_of_day..end_date.end_of_day - elsif start_date.present? - start_date.. - elsif end_date.present? - ..end_date - end - end end diff --git a/app/controllers/submissions_archived_controller.rb b/app/controllers/submissions_archived_controller.rb index d0e6d764..c13745ff 100644 --- a/app/controllers/submissions_archived_controller.rb +++ b/app/controllers/submissions_archived_controller.rb @@ -9,7 +9,14 @@ class SubmissionsArchivedController < ApplicationController .or(@submissions.where.not(templates: { archived_at: nil })) .preload(:created_by_user, template: :author) @submissions = Submissions.search(@submissions, params[:q], search_template: true) + @submissions = Submissions::Filter.call(@submissions, current_user, params) - @pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc)) + @submissions = if params[:completed_at_from].present? || params[:completed_at_to].present? + @submissions.order(Submitter.arel_table[:completed_at].maximum.desc) + else + @submissions.order(id: :desc) + end + + @pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events)) end end diff --git a/app/controllers/submissions_dashboard_controller.rb b/app/controllers/submissions_dashboard_controller.rb index aa17b5c4..164e893a 100644 --- a/app/controllers/submissions_dashboard_controller.rb +++ b/app/controllers/submissions_dashboard_controller.rb @@ -11,10 +11,17 @@ class SubmissionsDashboardController < ApplicationController .preload(:created_by_user, template: :author) @submissions = Submissions.search(@submissions, params[:q], search_template: true) + @submissions = Submissions::Filter.call(@submissions, current_user, params) @submissions = @submissions.pending if params[:status] == 'pending' @submissions = @submissions.completed if params[:status] == 'completed' - @pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events).order(id: :desc)) + @submissions = if params[:completed_at_from].present? || params[:completed_at_to].present? + @submissions.order(Submitter.arel_table[:completed_at].maximum.desc) + else + @submissions.order(id: :desc) + end + + @pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events)) end end diff --git a/app/controllers/submissions_filters_controller.rb b/app/controllers/submissions_filters_controller.rb new file mode 100644 index 00000000..bd5eae1e --- /dev/null +++ b/app/controllers/submissions_filters_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class SubmissionsFiltersController < ApplicationController + ALLOWED_NAMES = %w[ + author + completed_at + created_at + ].freeze + + skip_authorization_check + + def show + return head :not_found unless ALLOWED_NAMES.include?(params[:name]) + + render params[:name] + end +end diff --git a/app/controllers/templates_archived_submissions_controller.rb b/app/controllers/templates_archived_submissions_controller.rb index e0bdca25..bf023677 100644 --- a/app/controllers/templates_archived_submissions_controller.rb +++ b/app/controllers/templates_archived_submissions_controller.rb @@ -7,8 +7,15 @@ class TemplatesArchivedSubmissionsController < ApplicationController def index @submissions = @submissions.where.not(archived_at: nil) @submissions = Submissions.search(@submissions, params[:q], search_values: true) + @submissions = Submissions::Filter.call(@submissions, current_user, params) - @pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc)) + @submissions = if params[:completed_at_from].present? || params[:completed_at_to].present? + @submissions.order(Submitter.arel_table[:completed_at].maximum.desc) + else + @submissions.order(id: :desc) + end + + @pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events)) rescue ActiveRecord::RecordNotFound redirect_to root_path end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index 6b483df6..04d63e1c 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -4,51 +4,29 @@ class TemplatesController < ApplicationController load_and_authorize_resource :template before_action :load_base_template, only: %i[new create] - helper_method :created_at_range, :completed_at_range def show submissions = @template.submissions.accessible_by(current_ability) submissions = submissions.active if @template.archived_at.blank? submissions = Submissions.search(submissions, params[:q], search_values: true) + submissions = Submissions::Filter.call(submissions, current_user, params) @base_submissions = submissions submissions = submissions.pending if params[:status] == 'pending' submissions = submissions.completed if params[:status] == 'completed' - submissions = submissions.where(created_at: created_at_range) if created_at_range.present? - - if completed_at_range.present? - submissions = - Submission.from(submissions.completed - .joins(:submitters) - .select('submissions.*, max(completed_at) AS last_completed_at') - .group('submissions.id'), :submissions) - .where(last_completed_at: completed_at_range) - end - if params[:author].present? - submissions = - submissions.joins(:created_by_user) - .where("concat_ws(' ', NULLIF(users.first_name, ''), NULLIF(last_name, '')) = ?", params[:author]) - end + submissions = if params[:completed_at_from].present? || params[:completed_at_to].present? + submissions.order(Submitter.arel_table[:completed_at].maximum.desc) + else + submissions.order(id: :desc) + end - @pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events).order(id: :desc)) + @pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events)) rescue ActiveRecord::RecordNotFound redirect_to root_path end - def filter - return unless params[:filter] == 'author' - - submissions = @template.submissions.accessible_by(current_ability) - submissions = submissions.active if @template.archived_at.blank? - @creator_names = submissions.includes(:created_by_user) - .filter_map(&:created_by_user) - .map(&:full_name) - .uniq - .sort - end - def new @template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template end @@ -176,12 +154,4 @@ class TemplatesController < ApplicationController @base_template = Template.accessible_by(current_ability).find_by(id: params[:base_template_id]) end - - def created_at_range - @created_at_range ||= date_range(:created_at) - end - - def completed_at_range - @completed_at_range ||= date_range(:completed_at) - end end diff --git a/app/javascript/elements/set_date_button.js b/app/javascript/elements/set_date_button.js index f61543d7..409619de 100644 --- a/app/javascript/elements/set_date_button.js +++ b/app/javascript/elements/set_date_button.js @@ -1,17 +1,20 @@ export default class extends HTMLElement { connectedCallback () { - this.dateFrom = this.dataset.fromValue - this.dateTo = this.dataset.toValue - this.dateFromInput = document.getElementById(this.dataset.fromId) - this.dateToInput = document.getElementById(this.dataset.toId) - this.button.addEventListener('click', () => { - this.dateFromInput.value = this.dateFrom || '' - this.dateToInput.value = this.dateTo || '' + this.fromInput.value = this.dataset.fromValue || '' + this.toInput.value = this.dataset.toValue || '' }) } get button () { return this.querySelector('button') } + + get fromInput () { + return document.getElementById(this.dataset.fromId) + } + + get toInput () { + return document.getElementById(this.dataset.toId) + } } diff --git a/app/models/submission.rb b/app/models/submission.rb index 3972b6e5..e6aa35df 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -62,8 +62,11 @@ class Submission < ApplicationRecord through: :template, source: :documents_attachments scope :active, -> { where(archived_at: nil) } - scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct } - scope :completed, -> { where.not(id: pending.select(:submission_id)) } + scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).group(:id) } + scope :completed, lambda { + where.not(Submitter.where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id]) + .and(Submitter.arel_table[:completed_at].eq(nil))).select(1).arel.exists) + } enum :source, { invite: 'invite', diff --git a/app/views/shared/_search_input.html.erb b/app/views/shared/_search_input.html.erb index 3a003a19..5a6fae26 100644 --- a/app/views/shared/_search_input.html.erb +++ b/app/views/shared/_search_input.html.erb @@ -2,6 +2,11 @@ <% if params[:status].present? %> <% end %> + <% Submissions::Filter::ALLOWED_PARAMS.each do |key| %> + <% if params[key].present? %> + + <% end %> + <% end %> <% if params[:q].present? %>
diff --git a/app/views/submissions_archived/index.html.erb b/app/views/submissions_archived/index.html.erb index 846a044d..8a5216c1 100644 --- a/app/views/submissions_archived/index.html.erb +++ b/app/views/submissions_archived/index.html.erb @@ -1,22 +1,31 @@ +<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
<%= link_to root_path do %> ← <%= t('back_to_active') %> <% end %>
-
-
-

<%= t('submissions') %> <%= t('archived') %>

+
+
+
+

<%= t('submissions') %> <%= t('archived') %>

+
+
+ <% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %> + <%= render 'shared/search_input', placeholder: "#{t('search')}..." %> + <% end %> +
+
+
+ <%= render 'submissions_filters/applied_filters', filter_params: %> + <%= render 'submissions_filters/filter_button', filter_params: %>
- <% if params[:q].present? || @pagy.pages > 1 %> - <%= render 'shared/search_input', placeholder: "#{t('search')}..." %> - <% end %>
<% if @pagy.count > 0 %>
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true, archived: true } %>
-<% elsif params[:q].present? %> +<% elsif params[:q].present? || filter_params.present? %>
<%= t('submissions_not_found') %> diff --git a/app/views/submissions_dashboard/index.html.erb b/app/views/submissions_dashboard/index.html.erb index 040e2301..fc58043a 100644 --- a/app/views/submissions_dashboard/index.html.erb +++ b/app/views/submissions_dashboard/index.html.erb @@ -1,4 +1,5 @@ -<% is_show_tabs = @pagy.count >= 5 || params[:status].present? %> +<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %> +<% is_show_tabs = @pagy.count >= 5 || params[:status].present? || filter_params.present? %> <% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
@@ -10,7 +11,7 @@
- <% if params[:q].present? || @pagy.pages > 1 %> + <% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %> <%= render 'shared/search_input' %> <% end %> <% if can?(:create, ::Template) %> @@ -32,25 +33,31 @@ <% end %> <% end %> <% if is_show_tabs %> -
- -
- <%= svg_icon('list', class: 'w-5 h-5') %> - <%= t('all') %> -
-
- -
- <%= svg_icon('clock', class: 'w-5 h-5') %> - <%= t('pending') %> -
-
- -
- <%= svg_icon('circle_check', class: 'w-5 h-5') %> - <%= t('completed') %> -
-
+ <% end %> <% if @pagy.count > 0 %> @@ -58,10 +65,10 @@ <%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
<% end %> -<% if params[:q].blank? && params[:status].blank? && @pagy.count < 5 %> +<% if params[:q].blank? && params[:status].blank? && filter_params.blank? && @pagy.count < 5 %> <%= render 'templates/dropzone' %> <% end %> -<% if @submissions.present? || params[:q].blank? %> +<% if @submissions.present? || (params[:q].blank? && filter_params.blank?) %> <% if @pagy.pages > 1 %> <%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions', left_additional_html: view_archived_html %> <% else %> @@ -69,7 +76,7 @@ <%= view_archived_html %>
<% end %> -<% elsif params[:q].present? %> +<% elsif params[:q].present? || filter_params.present? %>
<%= t('submissions_not_found') %> diff --git a/app/views/submissions_filters/_applied_filters.html.erb b/app/views/submissions_filters/_applied_filters.html.erb new file mode 100644 index 00000000..ad988d71 --- /dev/null +++ b/app/views/submissions_filters/_applied_filters.html.erb @@ -0,0 +1,50 @@ +<% query_params = params.permit(:q, :status).merge(filter_params) %> +<% if query_params[:completed_at_from].present? || query_params[:completed_at_to].present? %> +
+ <%= link_to submissions_filter_path('completed_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %> + <%= svg_icon('calendar_check', class: 'w-5 h-5 shrink-0') %> + + <% if query_params[:completed_at_from] == query_params[:completed_at_to] %> + <%= l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) %> + <% else %> + <%= query_params[:completed_at_from].present? ? l(Date.parse(query_params[:completed_at_from]), locale: current_account.locale) : '∞' %> + - + <%= query_params[:completed_at_to].present? ? l(Date.parse(query_params[:completed_at_to]), locale: current_account.locale) : t('today') %> + <% end %> + + <% end %> + <%= link_to url_for(params.to_unsafe_h.except(:completed_at_from, :completed_at_to)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %> + <%= svg_icon('x', class: 'w-5 h-5') %> + <% end %> +
+<% end %> +<% if query_params[:created_at_from].present? || query_params[:created_at_to].present? %> +
+ <%= link_to submissions_filter_path('created_at', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %> + <%= svg_icon('calendar', class: 'w-5 h-5 shrink-0') %> + + <% if query_params[:created_at_from] == query_params[:created_at_to] %> + <%= l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) %> + <% else %> + <%= query_params[:created_at_from].present? ? l(Date.parse(query_params[:created_at_from]), locale: current_account.locale) : '∞' %> + - + <%= query_params[:created_at_to].present? ? l(Date.parse(query_params[:created_at_to]), locale: current_account.locale) : t('today') %> + <% end %> + + <% end %> + <%= link_to url_for(params.to_unsafe_h.except(:created_at_to, :created_at_from)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %> + <%= svg_icon('x', class: 'w-5 h-5') %> + <% end %> +
+<% end %> +<% if params[:author].present? %> +
+ <%= link_to submissions_filter_path('author', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %> + <%= svg_icon('user', class: 'w-5 h-5 shrink-0') %> + <%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %> + <% end %> + <%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %> + <%= svg_icon('x', class: 'w-5 h-5') %> + <% end %> +
+<% end %> diff --git a/app/views/submissions_filters/_date_buttons.html.erb b/app/views/submissions_filters/_date_buttons.html.erb new file mode 100644 index 00000000..3eb15e9c --- /dev/null +++ b/app/views/submissions_filters/_date_buttons.html.erb @@ -0,0 +1,39 @@ +<% current_time = Time.current.in_time_zone(current_account.timezone) %> +<% week_start = TimeUtils.timezone_abbr(current_account.timezone, current_time).in?(TimeUtils::US_TIMEZONES) ? :sunday : :monday %> +
+ + + + + + + + + + + + + + + + + + + + + +
diff --git a/app/views/submissions_filters/_filter_button.html.erb b/app/views/submissions_filters/_filter_button.html.erb new file mode 100644 index 00000000..cdf00fad --- /dev/null +++ b/app/views/submissions_filters/_filter_button.html.erb @@ -0,0 +1,27 @@ +<% query_params = params.permit(:q, :status).merge(filter_params) %> + diff --git a/app/views/submissions_filters/_filter_modal.html.erb b/app/views/submissions_filters/_filter_modal.html.erb new file mode 100644 index 00000000..04599244 --- /dev/null +++ b/app/views/submissions_filters/_filter_modal.html.erb @@ -0,0 +1,18 @@ +<%= render 'shared/turbo_modal', title: local_assigns[:title] do %> + <%= form_for '', url: params[:path], method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %> + <%= hidden_field_tag :status, params[:status] if params[:status].present? %> + <%= hidden_field_tag :q, params[:q] if params[:q].present? %> + <% local_assigns[:default_params].each do |key, value| %> + <%= hidden_field_tag(key, value) if value.present? %> + <% end %> + <%= yield %> +
+ <%= f.button button_title(title: t('apply'), disabled_with: t('applying')), name: nil, class: 'base-button' %> +
+ <% if params[:with_remove] %> +
+ <%= link_to t('remove_filter'), "#{params[:path]}?#{params.to_unsafe_h.slice(:q, :status).to_query}", class: 'link', data: { turbo_frame: :_top } %> +
+ <% end %> + <% end %> +<% end %> diff --git a/app/views/submissions_filters/author.html.erb b/app/views/submissions_filters/author.html.erb new file mode 100644 index 00000000..b9f3b27f --- /dev/null +++ b/app/views/submissions_filters/author.html.erb @@ -0,0 +1,7 @@ +<%= render 'filter_modal', title: t('author'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - ['author'])) do %> +
+
+ <%= select_tag :author, options_for_select(current_account.users.accessible_by(current_ability).where.not(role: :integration).where(account: current_account).map { |u| [u.full_name, u.email] }, params[:author].presence || current_user.email), required: true, class: 'base-select' %> +
+
+<% end %> diff --git a/app/views/submissions_filters/completed_at.html.erb b/app/views/submissions_filters/completed_at.html.erb new file mode 100644 index 00000000..0a0b6b6f --- /dev/null +++ b/app/views/submissions_filters/completed_at.html.erb @@ -0,0 +1,15 @@ +<%= render 'filter_modal', title: t('completed_at'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - %w[completed_at_to completed_at_from])) do %> +
+
+
+ <%= label_tag 'completed_at_from', t('from'), for: 'date_from', class: 'label text-sm' %> + <%= date_field_tag 'completed_at_from', params[:completed_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %> +
+
+ <%= label_tag 'completed_at_to', t('to'), for: 'date_to', class: 'label text-sm' %> + <%= date_field_tag 'completed_at_to', params[:completed_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %> +
+
+ <%= render 'date_buttons' %> +
+<% end %> diff --git a/app/views/submissions_filters/created_at.html.erb b/app/views/submissions_filters/created_at.html.erb new file mode 100644 index 00000000..2f089464 --- /dev/null +++ b/app/views/submissions_filters/created_at.html.erb @@ -0,0 +1,15 @@ +<%= render 'filter_modal', title: t('created_at'), default_params: params.permit(*(Submissions::Filter::ALLOWED_PARAMS - %w[created_at_to created_at_from])) do %> +
+
+
+ <%= label_tag 'created_at_from', t('from'), for: 'date_from', class: 'label text-sm' %> + <%= date_field_tag 'created_at_from', params[:created_at_from], id: 'date_from', class: 'base-input !h-10', autocomplete: 'off' %> +
+
+ <%= label_tag 'created_at_to', t('to'), for: 'date_to', class: 'label text-sm' %> + <%= date_field_tag 'created_at_to', params[:created_at_to], id: 'date_to', class: 'base-input !h-10', autocomplete: 'off' %> +
+
+ <%= render 'date_buttons' %> +
+<% end %> diff --git a/app/views/templates/filter.html.erb b/app/views/templates/filter.html.erb deleted file mode 100644 index 87d962f6..00000000 --- a/app/views/templates/filter.html.erb +++ /dev/null @@ -1,94 +0,0 @@ -<%= render 'shared/turbo_modal', title: t(params[:filter], default: '') do %> - <%= form_for @template, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %> - <%= hidden_field_tag :status, params[:status] if params[:status].present? %> - <%= hidden_field_tag :q, params[:q] if params[:q].present? %> -
- <% if params[:filter] == 'author' %> - <%= hidden_field_tag 'created_at[from]', params.dig(:created_at, :from) if params.dig(:created_at, :from).present? %> - <%= hidden_field_tag 'created_at[to]', params.dig(:created_at, :to) if params.dig(:created_at, :to).present? %> - <%= hidden_field_tag 'completed_at[from]', params.dig(:completed_at, :from) if params.dig(:completed_at, :from).present? %> - <%= hidden_field_tag 'completed_at[to]', params.dig(:completed_at, :to) if params.dig(:completed_at, :to).present? %> -
- <%= select_tag :author, options_for_select(@creator_names), required: true, class: 'base-select' %> -
- <% elsif params[:filter] == 'created_at' %> - <%= hidden_field_tag 'author', params[:author] if params[:author].present? %> - <%= hidden_field_tag 'completed_at[from]', params.dig(:completed_at, :from) if params.dig(:completed_at, :from).present? %> - <%= hidden_field_tag 'completed_at[to]', params.dig(:completed_at, :to) if params.dig(:completed_at, :to).present? %> -
-
- <%= label_tag 'created_at[from]', t('from'), for: 'date_from', class: 'label text-sm' %> - <%= date_field_tag 'created_at[from]', params.dig(:created_at, :from), id: 'date_from', class: 'base-input !select-sm !h-10"', autocomplete: 'off' %> -
-
- <%= label_tag 'created_at[to]', t('to'), for: 'date_to', class: 'label text-sm' %> - <%= date_field_tag 'created_at[to]', params.dig(:created_at, :to), id: 'date_to', class: 'base-input !select-sm !h-10"', autocomplete: 'off' %> -
-
- <% elsif params[:filter] == 'completed_at' %> - <%= hidden_field_tag 'author', params[:author] if params[:author].present? %> - <%= hidden_field_tag 'created_at[from]', params.dig(:created_at, :from) if params.dig(:created_at, :from).present? %> - <%= hidden_field_tag 'created_at[to]', params.dig(:created_at, :to) if params.dig(:created_at, :to).present? %> -
-
- <%= label_tag 'completed_at[from]', t('from'), for: 'date_from', class: 'label text-sm' %> - <%= date_field_tag 'completed_at[from]', params.dig(:completed_at, :from), id: 'date_from', class: 'base-input !select-sm !h-10"', autocomplete: 'off' %> -
-
- <%= label_tag 'completed_at[to]', t('to'), for: 'date_to', class: 'label text-sm' %> - <%= date_field_tag 'completed_at[to]', params.dig(:completed_at, :to), id: 'date_to', class: 'base-input !select-sm !h-10"', autocomplete: 'off' %> -
-
- <% end %> -
- <% if %w[created_at completed_at].include?(params[:filter]) %> - <% current_time = Time.current.in_time_zone(current_account.timezone) %> -
- - - - - - - - - - - - - - - - - - -
- <% end %> -
- <%= f.button button_title(title: t('apply'), disabled_with: t('applying')), name: nil, class: 'base-button' %> -
- <% if params[:filter].present? && params[params[:filter]].present? %> -
- <%= link_to t('remove_condition'), template_path(params[:id], params.to_unsafe_h.except(:id, :filter, :action, :controller, params[:filter].to_sym)), class: 'link', data: { turbo: false } %> -
- <% end %> - <% end %> -<% end %> diff --git a/app/views/templates/show.html.erb b/app/views/templates/show.html.erb index 82728490..8e222df9 100644 --- a/app/views/templates/show.html.erb +++ b/app/views/templates/show.html.erb @@ -1,7 +1,7 @@ <%= render 'title', template: @template %> -<% is_filter_active = params.slice(:author, :created_at, :completed_at).to_unsafe_h.any?(&:present?) %> -<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || is_filter_active %> -<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? || is_filter_active %> +<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %> +<% is_show_tabs = @pagy.pages > 1 || params[:q].present? || params[:status].present? || filter_params.present? %> +<% if !@pagy.count.zero? || params[:q].present? || params[:status].present? || filter_params.present? %>
@@ -10,7 +10,7 @@
- <% if params[:q].present? || params[:status].present? || is_filter_active || @pagy.pages > 1 %> + <% if params[:q].present? || params[:status].present? || filter_params.present? || @pagy.pages > 1 %> <%= render 'shared/search_input', title_selector: 'h2' %> <% end %> <%= link_to new_template_submissions_export_path(@template), class: 'hidden md:flex btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %> @@ -36,7 +36,7 @@ <%= t('all') %>
- <%= params[:status].blank? ? @pagy.count : @base_submissions.count %> + <%= params[:status].blank? && filter_params.blank? ? @pagy.count : @base_submissions.unscope(:group, :order).count %>
@@ -45,7 +45,7 @@ <%= t('pending') %>
- <%= params[:status] == 'pending' ? @pagy.count : @base_submissions.pending.count %> + <%= params[:status] == 'pending' && filter_params.blank? ? @pagy.count : @base_submissions.pending.unscope(:group, :order).count %>
@@ -54,86 +54,13 @@ <%= t('completed') %>
- <%= params[:status] == 'completed' ? @pagy.count : @base_submissions.completed.count %> + <%= params[:status] == 'completed' && filter_params.blank? ? @pagy.count : @base_submissions.completed.unscope(:group, :order).count %>
- <% if completed_at_range.present? %> -
- <%= link_to filter_templates_path(params.to_unsafe_h.except(:controller, :action).merge(filter: 'completed_at')), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1' do %> - <%= svg_icon('calendar_check', class: 'w-5 h-5 shrink-0') %> - - <% if completed_at_range.begin&.to_date == completed_at_range.end&.to_date %> - <%= l(completed_at_range.begin.to_date, locale: current_account.locale) %> - <% else %> - <%= completed_at_range.begin ? l(completed_at_range.begin.to_date, locale: current_account.locale) : '∞' %> - - - <%= completed_at_range.end ? l(completed_at_range.end.to_date, locale: current_account.locale) : t('today') %> - <% end %> - - <% end %> - <%= link_to url_for(params.to_unsafe_h.except(:completed_at)), class: 'rounded-lg ml-1 hover:bg-neutral-700 hover:text-white' do %> - <%= svg_icon('x', class: 'w-5 h-5') %> - <% end %> -
- <% end %> - <% if @created_at_range.present? %> -
- <%= link_to filter_templates_path(params.to_unsafe_h.except(:controller, :action).merge(filter: 'created_at')), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1' do %> - <%= svg_icon('calendar', class: 'w-5 h-5 shrink-0') %> - - <% if created_at_range.begin&.to_date == created_at_range.end&.to_date %> - <%= l(created_at_range.begin.to_date, locale: current_account.locale) %> - <% else %> - <%= created_at_range.begin ? l(created_at_range.begin.to_date, locale: current_account.locale) : '∞' %> - - - <%= created_at_range.end ? l(created_at_range.end.to_date, locale: current_account.locale) : t('today') %> - <% end %> - - <% end %> - <%= link_to url_for(params.to_unsafe_h.except(:created_at)), class: 'rounded-lg ml-1 hover:bg-neutral-700 hover:text-white' do %> - <%= svg_icon('x', class: 'w-5 h-5') %> - <% end %> -
- <% end %> - <% if params[:author].present? %> -
- <%= link_to filter_templates_path(params.to_unsafe_h.except(:controller, :action).merge(filter: 'author')), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full md:w-36' do %> - <%= svg_icon('user', class: 'w-5 h-5 shrink-0') %> - <%= params[:author] %> - <% end %> - <%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-neutral-700 hover:text-white' do %> - <%= svg_icon('x', class: 'w-5 h-5') %> - <% end %> -
- <% end %> - + <%= render 'submissions_filters/applied_filters', filter_params: %> + <%= render 'submissions_filters/filter_button', filter_params: %>
<% end %> @@ -148,7 +75,7 @@

<%= t('there_are_no_submissions') %>

- <% if @template.archived_at.blank? && params[:q].blank? %> + <% if @template.archived_at.blank? && params[:q].blank? && filter_params.blank? %>

<%= t('send_an_invitation_to_fill_and_complete_the_form') %>

@@ -169,7 +96,7 @@ <%= t('sign_it_yourself') %> <% end %> <% end %> - +
<% end %>
diff --git a/app/views/templates_archived_submissions/index.html.erb b/app/views/templates_archived_submissions/index.html.erb index 779b9e8e..eac39e1e 100644 --- a/app/views/templates_archived_submissions/index.html.erb +++ b/app/views/templates_archived_submissions/index.html.erb @@ -1,3 +1,5 @@ +<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %> +<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %> <%= render 'templates/title', template: @template %>
<%= link_to template_path(@template) do %> @@ -5,23 +7,33 @@ <%= t('back_to_active') %> <% end %>
-
-
-

<%= t('submissions') %> <%= t('archived') %>

-
-
- <%= render 'shared/search_input' %> - <%= link_to new_template_submissions_export_path(@template), class: 'order-3 md:order-1 btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %> - <%= svg_icon('download', class: 'w-6 h-6 stroke-2') %> - <%= t('export') %> - <% end %> +
+
+
+

<%= t('submissions') %> <%= t('archived') %>

+
+
+ <% if with_filters %> + <%= render 'shared/search_input' %> + <% end %> + <%= link_to new_template_submissions_export_path(@template), class: 'btn btn-ghost text-base', data: { turbo_frame: 'modal' } do %> + <%= svg_icon('download', class: 'w-6 h-6 stroke-2') %> + <%= t('export') %> + <% end %> +
+ <% if with_filters %> +
+ <%= render 'submissions_filters/applied_filters', filter_params: %> + <%= render 'submissions_filters/filter_button', filter_params: %> +
+ <% end %>
<% if @pagy.count > 0 %>
<%= render partial: 'templates/submission', collection: @submissions, locals: { template: @template, archived: true } %>
-<% elsif params[:q].present? %> +<% elsif params[:q].present? || filter_params.present? %>
<%= t('submissions_not_found') %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 6558454e..58869925 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -22,6 +22,7 @@ en: &en thanks: Thanks unarchive: Unarchive first_party: 'First Party' + remove_filter: Remove filter document_download_filename_format: Document download filename format document_name: Document Name docuseal_trusted_signature: DocuSeal Trusted Signature @@ -621,7 +622,7 @@ en: &en verified: Verified unverified: Unverified document: Document - completed_at: Completed At + completed_at: Completed at edit_recipient: Edit Recipient update_recipient: Update Recipient use_international_format_1xxx_: 'Use internatioanl format: +1xxx...' @@ -635,16 +636,16 @@ en: &en webhook_secret: Webhook Secret author: Author to: To - created_at: Created At - remove_condition: Remove condition + created_at: Created at apply: Apply applying: Applying today: Today - this_week: This Week - last_week: Last Week - this_month: This Month - last_month: Last Month - this_year: This Year + yesterday: Yesterday + this_week: This week + last_week: Last week + this_month: This month + last_month: Last month + this_year: This year submission_event_names: send_email_to_html: 'Email sent to %{submitter_name}' send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}' @@ -681,6 +682,7 @@ en: &en read: Read your data es: &es + remove_filter: Eliminar filtro document_download_filename_format: Formato del nombre del archivo de descarga del documento document_name: Nombre del documento unarchive: Desarchivar @@ -1299,10 +1301,10 @@ es: &es author: Autor to: A created_at: Creado el - remove_condition: Eliminar condición apply: Aplicar applying: Aplicando today: Hoy + yesterday: Ayer this_week: Esta Semana last_week: La Semana Pasada this_month: Este Mes @@ -1344,6 +1346,7 @@ es: &es read: Leer tus datos it: &it + remove_filter: Rimuovi filtro document_download_filename_format: Formato del nome file scaricato document_name: Nome del Documento unarchive: Ripristina @@ -1962,10 +1965,10 @@ it: &it author: Autore to: A created_at: Creato il - remove_condition: Rimuovi condizione apply: Applica applying: Applicazione today: Oggi + yesterday: Ieri this_week: Questa Settimana last_week: Settimana Scorsa this_month: Questo Mese @@ -2007,6 +2010,7 @@ it: &it read: Leggi i tuoi dati fr: &fr + remove_filter: Supprimer le filtre document_download_filename_format: Format du nom de fichier du téléchargement de document document_name: Nom du document unarchive: Désarchiver @@ -2626,10 +2630,10 @@ fr: &fr author: Auteur to: À created_at: Créé le - remove_condition: Supprimer la condition apply: Appliquer applying: Application en cours today: "Aujourd'hui" + yesterday: Hier this_week: Cette Semaine last_week: La Semaine Dernière this_month: Ce Mois-ci @@ -2671,6 +2675,7 @@ fr: &fr read: Lire vos données pt: &pt + remove_filter: Remover filtro document_download_filename_format: Formato do nome do arquivo de download do documento document_name: Nome do documento unarchive: Desarquivar @@ -3289,10 +3294,10 @@ pt: &pt author: Autor to: Para created_at: Criado em - remove_condition: Remover condição apply: Aplicar applying: Aplicando today: Hoje + yesterday: Ontem this_week: Esta Semana last_week: Semana Passada this_month: Este Mês @@ -3334,6 +3339,7 @@ pt: &pt read: Ler seus dados de: &de + remove_filter: Filter entfernen document_download_filename_format: Format des Dateinamens beim Herunterladen von Dokumenten document_name: Dokumentname unarchive: Wiederherstellen @@ -3952,10 +3958,10 @@ de: &de author: Autor to: An created_at: Erstellt am - remove_condition: Bedingung entfernen apply: Anwenden applying: Anwenden today: Heute + yesterday: Gestern this_week: Diese Woche last_week: Letzte Woche this_month: Dieser Monat diff --git a/config/routes.rb b/config/routes.rb index c1835f6a..dd568316 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -90,8 +90,8 @@ Rails.application.routes.draw do resources :folders, only: %i[show edit update destroy], controller: 'template_folders' resources :template_sharings_testing, only: %i[create] resources :templates, only: %i[index], controller: 'templates_dashboard' + resources :submissions_filters, only: %i[show], param: 'name' resources :templates, only: %i[new create edit update show destroy] do - get :filter, on: :collection resource :debug, only: %i[show], controller: 'templates_debug' if Rails.env.development? resources :documents, only: %i[create], controller: 'template_documents' resources :restore, only: %i[create], controller: 'templates_restore' diff --git a/lib/submissions.rb b/lib/submissions.rb index 1e7f9e44..ab9d9887 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -26,7 +26,7 @@ module Submissions arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%")) end - submissions.joins(:submitters).where(arel).distinct + submissions.joins(:submitters).where(arel).group(:id) end def update_template_fields!(submission) diff --git a/lib/submissions/filter.rb b/lib/submissions/filter.rb new file mode 100644 index 00000000..f4a2271a --- /dev/null +++ b/lib/submissions/filter.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Submissions + module Filter + ALLOWED_PARAMS = %w[ + author + completed_at_from + completed_at_to + created_at_from + created_at_to + ].freeze + + DATE_PARAMS = %w[ + completed_at_from + completed_at_to + created_at_from + created_at_to + ].freeze + + module_function + + def call(submissions, current_user, params) + filters = normalize_filter_params(params, current_user) + + if filters[:author].present? + user = current_user.account.users.find_by(email: filters[:author]) + submissions = submissions.where(created_by_user_id: user&.id || -1) + end + + submissions = submissions.where(created_at: filters[:created_at_from]..) if filters[:created_at_from].present? + + if filters[:created_at_to].present? + submissions = submissions.where(created_at: ..filters[:created_at_to].end_of_day) + end + + if filters[:completed_at_from].present? || filters[:completed_at_to].present? + completed_arel = Submitter.arel_table[:completed_at].maximum + submissions = submissions.completed.joins(:submitters).group(:id) + + if filters[:completed_at_from].present? + submissions = submissions.having(completed_arel.gteq(filters[:completed_at_from])) + end + + if filters[:completed_at_to].present? + submissions = submissions.having(completed_arel.lteq(filters[:completed_at_to].end_of_day)) + end + end + + submissions + end + + def normalize_filter_params(params, current_user) + tz = ActiveSupport::TimeZone[current_user.account.timezone] || Time.zone + + ALLOWED_PARAMS.each_with_object({}) do |key, acc| + next if params[key].blank? + + value = DATE_PARAMS.include?(key) ? tz.parse(params[key]) : params[key] + + acc[key.to_sym] = value + end + end + end +end diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index 96747e81..ab932be8 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -23,7 +23,7 @@ module Submissions RTL_REGEXP = TextUtils::RTL_REGEXP MAX_IMAGE_HEIGHT = 100 - US_TIMEZONES = %w[EST CST MST PST HST AKDT].freeze + US_TIMEZONES = TimeUtils::US_TIMEZONES module_function diff --git a/lib/time_utils.rb b/lib/time_utils.rb index 1f499195..9150b1b9 100644 --- a/lib/time_utils.rb +++ b/lib/time_utils.rb @@ -22,6 +22,8 @@ module TimeUtils DEFAULT_DATE_FORMAT_US = 'MM/DD/YYYY' DEFAULT_DATE_FORMAT = 'DD/MM/YYYY' + US_TIMEZONES = %w[EST CST MST PST HST AKDT].freeze + module_function def timezone_abbr(timezone, time = Time.current) diff --git a/spec/system/template_spec.rb b/spec/system/template_spec.rb index 2f017b05..0b512041 100644 --- a/spec/system/template_spec.rb +++ b/spec/system/template_spec.rb @@ -183,7 +183,7 @@ RSpec.describe 'Template' do end page.find('.dropdown', text: 'Filter').click - click_link 'Created At' + click_link 'Created at' within '#modal' do fill_in 'From', with: I18n.l(10.days.ago, format: '%Y-%m-%d') fill_in 'To', with: I18n.l(6.days.ago, format: '%Y-%m-%d') @@ -218,7 +218,7 @@ RSpec.describe 'Template' do end page.find('.dropdown', text: 'Filter').click - click_link 'Completed At' + click_link 'Completed at' within '#modal' do fill_in 'From', with: I18n.l(5.days.ago, format: '%Y-%m-%d') fill_in 'To', with: I18n.l(1.day.ago, format: '%Y-%m-%d')