add submissions dashboard view

pull/250/head
Pete Matsyburka 2 years ago
parent 5bb7ad571c
commit 2dba8ae366

@ -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?

@ -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

@ -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

@ -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

@ -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) => {

@ -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')
}
}

@ -0,0 +1,12 @@
<form action="<%= root_path %>" method="get" class="bg-base-200 px-1.5 rounded-xl py-1">
<toggle-cookies data-value="templates" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="Templates">
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9' : 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
<%= svg_icon('layout_grid', class: 'w-6 h-6 stroke-2') %>
</button>
</toggle-cookies>
<toggle-cookies data-value="submissions" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="Submissions">
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9' : 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
<%= svg_icon('layout_list', class: 'w-6 h-6 stroke-2') %>
</button>
</toggle-cookies>
</form>

@ -1,80 +0,0 @@
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center">
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
<div class="flex space-x-2">
<% 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') %>
<span class="hidden md:block">Create</span>
<% end %>
<% end %>
</div>
</div>
<% view_archived_html = capture do %>
<% if current_account.templates.where.not(archived_at: nil).exists? %>
<div>
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
</div>
<% end %>
<% end %>
<% if @template_folders.present? %>
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
</div>
<% end %>
<% if @templates.present? %>
<div class="grid gap-4 md:grid-cols-3">
<%= render partial: 'templates/template', collection: @templates %>
</div>
<% 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 %>
<input type="hidden" name="form_id" value="<%= form_id %>">
<button type="submit" class="hidden"></button>
<file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center">
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
<span>
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span>
<div class="font-medium mb-1">
Upload New Document
</div>
<div class="text-xs">
<span class="font-medium">Click to upload</span> or drag and drop
</div>
</span>
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
<div class="font-medium mb-1">
Uploading...
</div>
</span>
</div>
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
</div>
</label>
</file-dropzone>
<% 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 %>
<div class="mt-2">
<%= view_archived_html %>
</div>
<% end %>
<% elsif params[:q].present? %>
<div class="text-center">
<div class="mt-16 text-3xl font-semibold">
Templates not Found
</div>
</div>
<% end %>

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 12l14 0" />
<path d="M15 16l4 -4" />
<path d="M15 8l4 4" />
</svg>

After

Width:  |  Height:  |  Size: 361 B

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
<path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
</svg>

After

Width:  |  Height:  |  Size: 655 B

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" />
<path d="M4 14m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" />
</svg>

After

Width:  |  Height:  |  Size: 472 B

@ -9,7 +9,7 @@
</a>
</div>
<% end %>
<input id="search" name="q" value="<%= params[:q] %>" class="input input-ghost text-lg pr-10 -mr-12 <%= 'pl-8 input-outlined' if params[:q].present? %>">
<input id="search" name="q" value="<%= params[:q] %>" class="input input-ghost text-lg pr-10 -mr-12 <%= 'pl-8 input-outlined' if params[:q].present? %>" placeholder="<%= local_assigns[:placeholder] %>">
<button type="submit" title="Search" class="btn btn-ghost btn-circle" onclick="window.search.value || document.activeElement === window.search ? null : [event.preventDefault(), window.search.focus()]">
<span class="enabled">
<%= svg_icon('search', class: 'w-6 h-6 stroke-2') %>

@ -0,0 +1,24 @@
<div>
<%= link_to root_path do %>
&larr;
<span>Back to Active</span>
<% end %>
</div>
<div class="flex justify-between mb-4 items-center">
<h1 class="text-4xl font-bold">Submissions <span class="badge badge-outline badge-lg align-middle">Archived</span></h1>
<% if params[:q].present? || @pagy.pages > 1 %>
<%= render 'shared/search_input', placeholder: 'Search...' %>
<% end %>
</div>
<% if @pagy.count > 0 %>
<div class="space-y-4">
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
</div>
<% elsif params[:q].present? %>
<div class="text-center">
<div class="mt-16 text-3xl font-semibold">
Submissions not Found
</div>
</div>
<% end %>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions' %>

@ -0,0 +1,76 @@
<% is_show_tabs = @pagy.pages > 1 || params[:status].present? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center">
<div class="flex items-center">
<div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'submissions' %>
</div>
<h1 class="text-3xl sm:text-4xl font-bold">Submissions</h1>
</div>
<div class="flex space-x-2">
<% if params[:q].present? || @pagy.pages > 1 %>
<%= render 'shared/search_input' %>
<% end %>
<% if can?(:create, ::Template) %>
<span class="hidden sm:block">
<%= render 'templates/upload_button' %>
</span>
<%= 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') %>
<span class="hidden md:block">Create</span>
<% end %>
<% end %>
</div>
</div>
<% view_archived_html = capture do %>
<% if current_account.submissions.where.not(archived_at: nil).exists? %>
<div>
<a href="<%= submissions_archived_index_path %>" class="link text-sm">View Archived</a>
</div>
<% end %>
<% end %>
<% if is_show_tabs %>
<div class="flex items-center md:items-end flex-col space-y-2 md:space-y-0 md:flex-row md:space-x-2 mb-4">
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('list', class: 'w-5 h-5') %>
<span class="font-normal">All</span>
</div>
</a>
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('clock', class: 'w-5 h-5') %>
<span class="font-normal">Pending</span>
</div>
</a>
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
<span class="font-normal">Completed</span>
</div>
</a>
</div>
<% end %>
<% if @pagy.count > 0 %>
<div class="space-y-4">
<%= render partial: 'templates/submission', collection: @submissions, locals: { with_template: true } %>
</div>
<% end %>
<% if params[:q].blank? && @pagy.count < 5 %>
<%= render 'templates/dropzone' %>
<% end %>
<% if @submissions.present? || params[:q].blank? %>
<% if @pagy.pages > 1 %>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'submissions', left_additional_html: view_archived_html %>
<% else %>
<div class="mt-2">
<%= view_archived_html %>
</div>
<% end %>
<% elsif params[:q].present? %>
<div class="text-center">
<div class="mt-16 text-3xl font-semibold">
Submissions not Found
</div>
</div>
<% end %>

@ -0,0 +1,30 @@
<%= form_for '', url: templates_upload_path, id: form_id = SecureRandom.uuid, method: :post, class: 'mt-8 block', html: { enctype: 'multipart/form-data' } do %>
<input type="hidden" name="form_id" value="<%= form_id %>">
<button type="submit" class="hidden"></button>
<file-dropzone data-submit-on-upload="true" class="w-full">
<label for="file_dropzone_input" class="w-full block h-52 relative hover:bg-base-200/30 rounded-xl border border-2 border-base-300 border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center">
<span data-target="file-dropzone.icon" class="flex flex-col items-center">
<span>
<%= svg_icon('cloud_upload', class: 'w-10 h-10') %>
</span>
<div class="font-medium mb-1">
Upload New Document
</div>
<div class="text-xs">
<span class="font-medium">Click to upload</span> or drag and drop
</div>
</span>
<span data-target="file-dropzone.loading" class="flex flex-col items-center hidden">
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
<div class="font-medium mb-1">
Uploading...
</div>
</span>
</div>
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls' if Docuseal.multitenant? %>" multiple>
</div>
</label>
</file-dropzone>
<% end %>

@ -1,131 +1,55 @@
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-5 md:px-6 py-5 md:items-center">
<% 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 %>
<div>
<% submitter = submitters.first %>
<div class="flex items-center space-x-4">
<span class="flex flex-col md:flex-row md:items-center gap-3">
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %>
</span>
</div>
<span class="text-lg break-all flex items-center">
<%= submitter.name || submitter.email || submitter.phone %>
<div class="bg-base-200 rounded-2xl flex flex-col sm:flex-row items-strech">
<% if local_assigns[:with_template] %>
<% template = submission.template %>
<a href="<%= template_path(template) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
<div>
<div class="font-medium items-start w-full group-hover:link text-sm flex space-x-1">
<%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %>
<span style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
<%= template.name %>
<%= svg_icon('arrow_right', class: 'w-4 h-4 sm:inline group-hover:visible invisible hidden') %>
</span>
</span>
</div>
</div>
</div>
<div class="flex space-x-2 items-center">
<% if submitter.completed_at? %>
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="w-full md:w-fit">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline">Download</span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span class="inline">Downloa...</span>
</span>
</download-button>
</button>
</form>
<% else %>
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
<span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<span class="inline shrink-0">Sign now</span>
</span>
</button>
</form>
<% else %>
<div class="flex-1 md:flex-none">
<%= 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' %>
</div>
<% end %>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
<div class="flex-shrink-0 sm:mt-2">
<div class="flex text-xs flex-col text-base-content/60 space-y-0.5">
<span class="flex items-center space-x-1">
<%= svg_icon('user', class: 'w-4 h-4 flex-shrink-0') %>
<span><%= (submission.created_by_user || template.author).full_name.presence || (submission.created_by_user || template.author).email.to_s.sub(/\+\w+@/, '@') %></span>
</span>
<span class="flex items-center space-x-1">
<%= svg_icon('calendar', class: 'w-4 h-4 flex-shrink-0') %>
<span><%= l(submission.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
</span>
</div>
</div>
<% 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 %>
</div>
<% else %>
<div class="space-y-1 w-full md:mr-2">
<div class="flex flex-col md:flex-row md:items-center gap-3">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
<%= latest_submitter.status %>
</span>
</div>
<% end %>
<div class="w-full <%= is_submission_completed ? 'space-y-1' : 'space-y-4' %> md:space-y-0">
<% submitters.each_with_index do |submitter, index| %>
<div class="relative flex justify-between items-start md:items-center space-x-3">
<span class="flex flex-col md:flex-row md:items-center gap-2">
<% unless is_submission_completed %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
<%= submitter.status %>
</span>
</div>
<% end %>
<span class="text-lg break-all">
<%= submitter.name || submitter.email || submitter.phone %>
</span>
</a>
<% end %>
<a href="<%= submission_path(submission) %>" class="w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between px-5 md:px-6 pb-5 md:items-center pt-5">
<% 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 %>
<div>
<% submitter = submitters.first %>
<div class="flex items-center space-x-4">
<span class="flex flex-col md:flex-row md:items-center gap-3">
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %>
</span>
<% if submitter.completed_at? && !is_submission_completed %>
<form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="absolute md:relative top-0 right-0 btn btn-xs btn-neutral text-white md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
<span class="inline">Download</span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
<span class="inline">Downloa...</span>
</span>
</download-button>
</button>
</form>
<% elsif !is_submission_completed %>
<div class="relative flex items-center space-x-3">
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
<button onclick="event.stopPropagation()" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<span class="inline shrink-0">Sign now</span>
</span>
</button>
</form>
<% 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 %>
</div>
<% end %>
</div>
<% end %>
<span class="text-lg break-all flex items-center">
<%= submitter.name || submitter.email || submitter.phone %>
</span>
</span>
</div>
</div>
</div>
<div class="flex space-x-1 md:space-x-2 items-center">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="flex-1 md:flex-none">
<form onsubmit="event.preventDefault()" class="w-full md:w-fit">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
<div class="flex space-x-2 items-center">
<% if submitter.completed_at? %>
<form onsubmit="event.preventDefault()" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="w-full md:w-fit">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-full md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline">Download</span>
@ -137,14 +61,122 @@
</download-button>
</button>
</form>
<% else %>
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
<span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<span class="inline shrink-0">Sign now</span>
</span>
</button>
</form>
<% else %>
<div class="flex-1 md:flex-none">
<%= 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' %>
</div>
<% end %>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
</div>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
<% if !submission.archived_at? && can?(:destroy, submission) %>
<span data-tip="Archive" class="sm:tooltip tooltip-top">
<%= 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()' %>
</span>
<% end %>
</div>
<% 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 %>
</div>
<% end %>
</a>
<% else %>
<div class="space-y-1 w-full md:mr-2">
<div class="flex flex-col md:flex-row md:items-center gap-3">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="tooltip flex" data-tip="<%= l(latest_submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[latest_submitter.status] %> md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
<%= latest_submitter.status %>
</span>
</div>
<% end %>
<div class="w-full <%= is_submission_completed ? 'space-y-1' : 'space-y-4' %> md:space-y-0">
<% submitters.each_with_index do |submitter, index| %>
<div class="relative flex justify-between items-start md:items-center space-x-3">
<span class="flex flex-col md:flex-row md:items-center gap-2">
<% unless is_submission_completed %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
<%= submitter.status %>
</span>
</div>
<% end %>
<span class="text-lg break-all">
<%= submitter.name || submitter.email || submitter.phone %>
</span>
</span>
<% if submitter.completed_at? && !is_submission_completed %>
<form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="absolute md:relative top-0 right-0 btn btn-xs btn-neutral text-white md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-4 h-4 stroke-2') %>
<span class="inline">Download</span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin') %>
<span class="inline">Downloa...</span>
</span>
</download-button>
</button>
</form>
<% elsif !is_submission_completed %>
<div class="relative flex items-center space-x-3">
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">
<button onclick="event.stopPropagation()" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2">
<%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %>
<span class="inline shrink-0">Sign now</span>
</span>
</button>
</form>
<% 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 %>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<div class="flex space-x-1 md:space-x-2 items-center">
<% if is_submission_completed %>
<% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
<div class="flex-1 md:flex-none">
<form onsubmit="event.preventDefault()" class="w-full md:w-fit">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(latest_submitter.slug) %>" class="btn btn-sm btn-neutral text-white md:w-36">
<span class="flex items-center justify-center space-x-1 md:space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span class="inline">Download</span>
</span>
<span class="flex items-center justify-center space-x-1 md:space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span class="inline">Downloa...</span>
</span>
</download-button>
</button>
</form>
</div>
<% end %>
<div class="flex-1 md:flex-none">
<span class="btn btn-outline btn-sm w-full md:w-24">View</span>
</div>
<% unless submission.archived_at? %>
<span data-tip="Archive" class="sm:tooltip tooltip-top">
<%= 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()' %>
</span>
<% end %>
</div>
<% end %>
</a>
</div>

@ -7,7 +7,7 @@
<div class="flex justify-between mb-4 items-center">
<h1 class="text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates <span class="badge badge-outline badge-lg align-middle">Archived</span></h1>
<% if params[:q].present? || @pagy.pages > 1 %>
<%= render 'shared/search_input' %>
<%= render 'shared/search_input', placeholder: 'Search...' %>
<% end %>
</div>
<% if @pagy.count > 0 %>

@ -0,0 +1,61 @@
<% has_archived = current_account.templates.where.not(archived_at: nil).exists? %>
<% if Docuseal.demo? %><%= render 'shared/demo_alert' %><% end %>
<div class="flex justify-between mb-4 items-center">
<div class="flex items-center">
<% if has_archived || @pagy.count > 0 %>
<div class="mr-2">
<%= render 'dashboard/toggle_view', selected: 'templates' %>
</div>
<% end %>
<h1 class="text-3xl sm:text-4xl font-bold"><span class="hidden md:inline">Document</span> Templates</h1>
</div>
<div class="flex space-x-2">
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
<%= render 'shared/search_input' %>
<% end %>
<% if can?(:create, ::Template) %>
<span class="hidden sm:block">
<%= render 'templates/upload_button' %>
</span>
<%= 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') %>
<span class="hidden md:block">Create</span>
<% end %>
<% end %>
</div>
</div>
<% view_archived_html = capture do %>
<% if has_archived %>
<div>
<a href="<%= templates_archived_index_path %>" class="link text-sm">View Archived</a>
</div>
<% end %>
<% end %>
<% if @template_folders.present? %>
<div class="grid gap-4 md:grid-cols-3 <%= 'mb-6' if @templates.present? %>">
<%= render partial: 'template_folders/folder', collection: @template_folders, as: :folder %>
</div>
<% end %>
<% if @templates.present? %>
<div class="grid gap-4 md:grid-cols-3">
<%= render partial: 'templates/template', collection: @templates %>
</div>
<% 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 %>
<div class="mt-2">
<%= view_archived_html %>
</div>
<% end %>
<% elsif params[:q].present? %>
<div class="text-center">
<div class="mt-16 text-3xl font-semibold">
Templates not Found
</div>
</div>
<% end %>

@ -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'

@ -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

Loading…
Cancel
Save