diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4047dda3..fe309a80 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -7,66 +7,18 @@ class DashboardController < ApplicationController before_action :maybe_render_landing before_action :maybe_redirect_mfa_setup - load_and_authorize_resource :template_folder, parent: false - load_and_authorize_resource :template, parent: false - - SHOW_TEMPLATES_FOLDERS_THRESHOLD = 9 - TEMPLATES_PER_PAGE = 12 - FOLDERS_PER_PAGE = 18 + skip_authorization_check def index - @template_folders = filter_template_folders(@template_folders) - - @pagy, @template_folders = pagy( - @template_folders, - items: FOLDERS_PER_PAGE, - page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1 - ) - - if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD - @templates = @templates.none + if cookies.permanent[:dashboard_view] == 'submissions' + SubmissionsDashboardController.dispatch(:index, request, response) else - @template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME } - @templates = filter_templates(@templates) - - items = - if @template_folders.size < 4 - TEMPLATES_PER_PAGE - else - (@template_folders.size < 7 ? 9 : 6) - end - - @pagy, @templates = pagy(@templates, items:) + TemplatesDashboardController.dispatch(:index, request, response) end end private - def filter_template_folders(template_folders) - rel = template_folders.joins(:active_templates) - .order(id: :desc) - .distinct - - TemplateFolders.search(rel, params[:q]) - end - - def filter_templates(templates) - rel = templates.active.preload(:author).order(id: :desc) - - if params[:q].blank? - if Docuseal.multitenant? && !current_account.testing? - rel = rel.where(folder_id: current_account.default_template_folder.id) - else - shared_template_ids = - TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id) - - rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids)) - end - end - - Templates.search(rel, params[:q]) - end - def maybe_redirect_product_url return if !Docuseal.multitenant? || signed_in? diff --git a/app/controllers/submissions_archived_controller.rb b/app/controllers/submissions_archived_controller.rb new file mode 100644 index 00000000..d0e6d764 --- /dev/null +++ b/app/controllers/submissions_archived_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class SubmissionsArchivedController < ApplicationController + load_and_authorize_resource :submission, parent: false + + def index + @submissions = @submissions.joins(:template) + @submissions = @submissions.where.not(archived_at: nil) + .or(@submissions.where.not(templates: { archived_at: nil })) + .preload(:created_by_user, template: :author) + @submissions = Submissions.search(@submissions, params[:q], search_template: true) + + @pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc)) + end +end diff --git a/app/controllers/submissions_dashboard_controller.rb b/app/controllers/submissions_dashboard_controller.rb new file mode 100644 index 00000000..57fb1e47 --- /dev/null +++ b/app/controllers/submissions_dashboard_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class SubmissionsDashboardController < ApplicationController + load_and_authorize_resource :submission, parent: false + + def index + @submissions = @submissions.joins(:template) + + @submissions = @submissions.where(archived_at: nil) + .where(templates: { archived_at: nil }) + .preload(:created_by_user, template: :author) + + @submissions = Submissions.search(@submissions, params[:q], search_template: true) + + @submissions = @submissions.pending if params[:status] == 'pending' + @submissions = @submissions.completed if params[:status] == 'completed' + + @pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc)) + end +end diff --git a/app/controllers/templates_dashboard_controller.rb b/app/controllers/templates_dashboard_controller.rb new file mode 100644 index 00000000..12a6db8a --- /dev/null +++ b/app/controllers/templates_dashboard_controller.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class TemplatesDashboardController < ApplicationController + load_and_authorize_resource :template_folder, parent: false + load_and_authorize_resource :template, parent: false + + SHOW_TEMPLATES_FOLDERS_THRESHOLD = 9 + TEMPLATES_PER_PAGE = 12 + FOLDERS_PER_PAGE = 18 + + def index + @template_folders = filter_template_folders(@template_folders) + + @pagy, @template_folders = pagy( + @template_folders, + items: FOLDERS_PER_PAGE, + page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1 + ) + + if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD + @templates = @templates.none + else + @template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME } + @templates = filter_templates(@templates) + + items = + if @template_folders.size < 4 + TEMPLATES_PER_PAGE + else + (@template_folders.size < 7 ? 9 : 6) + end + + @pagy, @templates = pagy(@templates, items:) + end + end + + private + + def filter_template_folders(template_folders) + rel = template_folders.joins(:active_templates) + .order(id: :desc) + .distinct + + TemplateFolders.search(rel, params[:q]) + end + + def filter_templates(templates) + rel = templates.active.preload(:author).order(id: :desc) + + if params[:q].blank? + if Docuseal.multitenant? && !current_account.testing? + rel = rel.where(folder_id: current_account.default_template_folder.id) + else + shared_template_ids = + TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id) + + rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids)) + end + end + + Templates.search(rel, params[:q]) + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index fc2e95ce..5a209c92 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -6,6 +6,7 @@ import TemplateBuilder from './template_builder/builder' import ImportList from './template_builder/import_list' import ToggleVisible from './elements/toggle_visible' +import ToggleCookies from './elements/toggle_cookies' import DisableHidden from './elements/disable_hidden' import TurboModal from './elements/turbo_modal' import FileDropzone from './elements/file_dropzone' @@ -56,6 +57,7 @@ window.customElements.define('signature-form', SignatureForm) window.customElements.define('submit-form', SubmitForm) window.customElements.define('prompt-password', PromptPassword) window.customElements.define('emails-textarea', EmailsTextarea) +window.customElements.define('toggle-cookies', ToggleCookies) document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody) document.addEventListener('turbo:submit-end', async (event) => { diff --git a/app/javascript/elements/toggle_cookies.js b/app/javascript/elements/toggle_cookies.js new file mode 100644 index 00000000..8d030cb3 --- /dev/null +++ b/app/javascript/elements/toggle_cookies.js @@ -0,0 +1,17 @@ +export default class extends HTMLElement { + connectedCallback () { + this.button.addEventListener('click', () => { + const expirationDate = new Date() + + expirationDate.setFullYear(expirationDate.getFullYear() + 10) + + const expires = expirationDate.toUTCString() + + document.cookie = this.dataset.key + '=' + this.dataset.value + '; expires=' + expires + '; path=/' + }) + } + + get button () { + return this.querySelector('button') + } +} diff --git a/app/views/dashboard/_toggle_view.html.erb b/app/views/dashboard/_toggle_view.html.erb new file mode 100644 index 00000000..1cc869fa --- /dev/null +++ b/app/views/dashboard/_toggle_view.html.erb @@ -0,0 +1,12 @@ +
+ + + + + + +
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb deleted file mode 100644 index 4fa74712..00000000 --- a/app/views/dashboard/index.html.erb +++ /dev/null @@ -1,80 +0,0 @@ -<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %> -
-

Templates

-
- <% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %> - <%= render 'shared/search_input' %> - <% end %> - <% if can?(:create, ::Template) %> - <%= render 'templates/upload_button' %> - <%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %> - <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %> - - <% end %> - <% end %> -
-
-<% view_archived_html = capture do %> - <% if current_account.templates.where.not(archived_at: nil).exists? %> -
- View Archived -
- <% end %> -<% end %> -<% if @template_folders.present? %> -
- <%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %> -
-<% end %> -<% if @templates.present? %> -
- <%= render partial: 'templates/template', collection: @templates %> -
-<% end %> -<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %> - <%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %> - - - - - - <% end %> -<% end %> -<% if @templates.present? || params[:q].blank? %> - <% if @pagy.pages > 1 %> - <%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %> - <% else %> -
- <%= view_archived_html %> -
- <% end %> -<% elsif params[:q].present? %> -
-
- Templates not Found -
-
-<% end %> diff --git a/app/views/icons/_arrow_right.html.erb b/app/views/icons/_arrow_right.html.erb new file mode 100644 index 00000000..f9bbfd29 --- /dev/null +++ b/app/views/icons/_arrow_right.html.erb @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/views/icons/_layout_grid.html.erb b/app/views/icons/_layout_grid.html.erb new file mode 100644 index 00000000..f471022f --- /dev/null +++ b/app/views/icons/_layout_grid.html.erb @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/views/icons/_layout_list.html.erb b/app/views/icons/_layout_list.html.erb new file mode 100644 index 00000000..ab2c7721 --- /dev/null +++ b/app/views/icons/_layout_list.html.erb @@ -0,0 +1,5 @@ + + + + + diff --git a/app/views/shared/_search_input.html.erb b/app/views/shared/_search_input.html.erb index fa50725a..c24eb1e5 100644 --- a/app/views/shared/_search_input.html.erb +++ b/app/views/shared/_search_input.html.erb @@ -9,7 +9,7 @@ <% end %> - + + + + +<% end %> diff --git a/app/views/templates/_submission.html.erb b/app/views/templates/_submission.html.erb index e2a8c579..001d651c 100644 --- a/app/views/templates/_submission.html.erb +++ b/app/views/templates/_submission.html.erb @@ -1,131 +1,55 @@ <% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %> - - <% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %> - <% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %> - <% if submitters.size == 1 %> -
- <% submitter = submitters.first %> -
- -
- - <%= submitter.status %> - -
- - <%= submitter.name || submitter.email || submitter.phone %> +
-
- <% if submitter.completed_at? %> -
- -
- <% else %> - <% if current_user.email == submitter.email %> -
- -
- <% else %> -
- <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy', copied_title_md: 'Copied' %> -
- <% end %> - <% end %> -
- View +
+
+ + <%= svg_icon('user', class: 'w-4 h-4 flex-shrink-0') %> + <%= (submission.created_by_user || template.author).full_name.presence || (submission.created_by_user || template.author).email.to_s.sub(/\+\w+@/, '@') %> + + + <%= svg_icon('calendar', class: 'w-4 h-4 flex-shrink-0') %> + <%= l(submission.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %> + +
- <% if !submission.archived_at? && can?(:destroy, submission) %> - <%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %> - <% end %> -
- <% else %> -
-
- <% if is_submission_completed %> - <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> -
- - <%= latest_submitter.status %> - -
- <% end %> -
- <% submitters.each_with_index do |submitter, index| %> - -
- <% if is_submission_completed %> - <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> -
-
-
+ <% else %> + <% if current_user.email == submitter.email %> +
+ +
+ <% else %> +
+ <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy', copied_title_md: 'Copied' %> +
+ <% end %> + <% end %> +
+ View
- <% end %> -
- View + <% if !submission.archived_at? && can?(:destroy, submission) %> + + <%= button_to button_title(title: nil, disabled_with: 'Arch', icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Archive', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %> + + <% end %>
- <% unless submission.archived_at? %> - <%= button_to button_title(title: nil, disabled_with: 'Remov', icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %> - <% end %> -
- <% end %> -
+ <% else %> +
+
+ <% if is_submission_completed %> + <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> +
+ + <%= latest_submitter.status %> + +
+ <% end %> +
+ <% submitters.each_with_index do |submitter, index| %> +
+ + <% unless is_submission_completed %> +
+ + <%= submitter.status %> + +
+ <% end %> + + <%= submitter.name || submitter.email || submitter.phone %> + +
+ <% if submitter.completed_at? && !is_submission_completed %> +
+ +
+ <% elsif !is_submission_completed %> +
+ <% if current_user.email == submitter.email %> +
+ +
+ <% else %> + <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'absolute md:relative top-0 right-0 btn btn-xs text-xs btn-neutral text-white w-28 md:w-36 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link', copy_title_md: 'Copy Link', copied_title_md: 'Copied' %> + <% end %> +
+ <% end %> +
+ <% end %> +
+
+
+
+ <% if is_submission_completed %> + <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %> +
+
+ +
+
+ <% end %> +
+ View +
+ <% unless submission.archived_at? %> + + <%= button_to button_title(title: nil, disabled_with: 'Arch', icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: 'Archive', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %> + + <% end %> +
+ <% end %> + +
diff --git a/app/views/templates_archived/index.html.erb b/app/views/templates_archived/index.html.erb index bcdee860..9b20fff4 100644 --- a/app/views/templates_archived/index.html.erb +++ b/app/views/templates_archived/index.html.erb @@ -7,7 +7,7 @@

Templates Archived

<% if params[:q].present? || @pagy.pages > 1 %> - <%= render 'shared/search_input' %> + <%= render 'shared/search_input', placeholder: 'Search...' %> <% end %>
<% if @pagy.count > 0 %> diff --git a/app/views/templates_dashboard/index.html.erb b/app/views/templates_dashboard/index.html.erb new file mode 100644 index 00000000..919c6422 --- /dev/null +++ b/app/views/templates_dashboard/index.html.erb @@ -0,0 +1,61 @@ +<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %> +<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %> +
+
+ <% if has_archived || @pagy.count > 0 %> +
+ <%= render 'dashboard/toggle_view', selected: 'templates' %> +
+ <% end %> +

Templates

+
+
+ <% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %> + <%= render 'shared/search_input' %> + <% end %> + <% if can?(:create, ::Template) %> + + <%= link_to new_template_path, class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %> + <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %> + + <% end %> + <% end %> +
+
+<% view_archived_html = capture do %> + <% if has_archived %> + + <% end %> +<% end %> +<% if @template_folders.present? %> +
+ <%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %> +
+<% end %> +<% if @templates.present? %> +
+ <%= render partial: 'templates/template', collection: @templates %> +
+<% end %> +<% if params[:q].blank? && @pagy.pages == 1 && ((@template_folders.size < 10 && @templates.size.zero?) || (@template_folders.size < 7 && @templates.size < 4) || (@template_folders.size < 4 && @templates.size < 7)) %> + <%= render 'templates/dropzone' %> +<% end %> +<% if @templates.present? || params[:q].blank? %> + <% if @pagy.pages > 1 %> + <%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', left_additional_html: view_archived_html %> + <% else %> +
+ <%= view_archived_html %> +
+ <% end %> +<% elsif params[:q].present? %> +
+
+ Templates not Found +
+
+<% end %> diff --git a/config/routes.rb b/config/routes.rb index 85b36d91..9c618b29 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,6 +50,8 @@ Rails.application.routes.draw do resources :enquiries, only: %i[create] resources :users, only: %i[new create edit update destroy] resource :user_signature, only: %i[edit update destroy] + resources :submissions_archived, only: %i[index], path: 'submissions/archived' + resources :submissions, only: %i[index], controller: 'submissions_dashboard' resources :submissions, only: %i[show destroy] resources :console_redirect, only: %i[index] resources :upgrade, only: %i[index], controller: 'console_redirect' @@ -61,9 +63,10 @@ Rails.application.routes.draw do authenticated do resource :templates_upload, only: %i[show], path: 'new' end - resources :templates_archived, only: %i[index], path: 'archived' + resources :templates_archived, only: %i[index], path: 'templates/archived' 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 :templates, only: %i[new create edit update show destroy] do 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 c238e834..655d1e65 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -7,7 +7,7 @@ module Submissions module_function - def search(submissions, keyword, search_values: false) + def search(submissions, keyword, search_values: false, search_template: false) return submissions if keyword.blank? term = "%#{keyword.downcase}%" @@ -20,6 +20,12 @@ module Submissions arel = arel.or(Arel::Table.new(:submitters)[:values].matches(term)) if search_values + if search_template + submissions = submissions.joins(:template) + + arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%")) + end + submissions.joins(:submitters).where(arel).distinct end