add subfolders
This commit is contained in:
@@ -11,8 +11,9 @@ module Api
|
||||
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
|
||||
|
||||
submitters = paginate(
|
||||
submitters.preload(template: :folder, submission: [:submitters, { audit_trail_attachment: :blob,
|
||||
combined_document_attachment: :blob }],
|
||||
submitters.preload(template: { folder: :parent_folder },
|
||||
submission: [:submitters, { audit_trail_attachment: :blob,
|
||||
combined_document_attachment: :blob }],
|
||||
documents_attachments: :blob, attachments_attachments: :blob),
|
||||
field: :completed_at
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ module Api
|
||||
submissions = filter_submissions(submissions, params)
|
||||
|
||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||
template: :folder,
|
||||
template: { folder: :parent_folder },
|
||||
combined_document_attachment: :blob,
|
||||
audit_trail_attachment: :blob))
|
||||
|
||||
@@ -104,9 +104,10 @@ module Api
|
||||
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||
|
||||
if params[:template_folder].present?
|
||||
folder_ids = TemplateFolder.accessible_by(current_ability).where(name: params[:template_folder]).pluck(:id)
|
||||
folders =
|
||||
TemplateFolders.filter_by_full_name(TemplateFolder.accessible_by(current_ability), params[:template_folder])
|
||||
|
||||
submissions = submissions.joins(:template).where(template: { folder_id: folder_ids })
|
||||
submissions = submissions.joins(:template).where(template: { folder_id: folders.pluck(:id) })
|
||||
end
|
||||
|
||||
if params.key?(:archived)
|
||||
|
||||
@@ -7,7 +7,7 @@ module Api
|
||||
def index
|
||||
templates = filter_templates(@templates, params)
|
||||
|
||||
templates = paginate(templates.preload(:author, :folder))
|
||||
templates = paginate(templates.preload(:author, folder: :parent_folder))
|
||||
|
||||
schema_documents =
|
||||
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
|
||||
@@ -92,9 +92,9 @@ module Api
|
||||
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
||||
|
||||
if params[:folder].present?
|
||||
folder_ids = TemplateFolder.accessible_by(current_ability).where(name: params[:folder]).pluck(:id)
|
||||
folders = TemplateFolders.filter_by_full_name(TemplateFolder.accessible_by(current_ability), params[:folder])
|
||||
|
||||
templates = templates.where(folder_id: folder_ids)
|
||||
templates = templates.where(folder_id: folders.pluck(:id))
|
||||
end
|
||||
|
||||
templates
|
||||
|
||||
@@ -3,14 +3,31 @@
|
||||
class TemplateFoldersAutocompleteController < ApplicationController
|
||||
load_and_authorize_resource :template_folder, parent: false
|
||||
|
||||
LIMIT = 100
|
||||
LIMIT = 30
|
||||
|
||||
def index
|
||||
templates_query = Template.accessible_by(current_ability).where(archived_at: nil)
|
||||
parent_name, name =
|
||||
if params[:parent_name].present?
|
||||
[params[:parent_name], params[:q]]
|
||||
else
|
||||
params[:q].to_s.split(' /', 2).map(&:squish)
|
||||
end
|
||||
|
||||
template_folders = @template_folders.where(id: templates_query.select(:folder_id))
|
||||
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
|
||||
if name
|
||||
parent_folder = @template_folders.find_by(name: parent_name, parent_folder_id: nil)
|
||||
else
|
||||
name = parent_name
|
||||
end
|
||||
|
||||
render json: template_folders.as_json(only: %i[name archived_at])
|
||||
template_folders = TemplateFolders.filter_active_folders(@template_folders.where(parent_folder:),
|
||||
Template.accessible_by(current_ability))
|
||||
|
||||
name = name.to_s.downcase
|
||||
|
||||
template_folders = TemplateFolders.search(template_folders, name).order(id: :desc).limit(LIMIT)
|
||||
|
||||
render json: template_folders.preload(:parent_folder)
|
||||
.sort_by { |e| e.name.downcase.index(name) || Float::MAX }
|
||||
.as_json(only: %i[name archived_at], methods: %i[full_name])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,13 +5,39 @@ class TemplateFoldersController < ApplicationController
|
||||
|
||||
helper_method :selected_order
|
||||
|
||||
def show
|
||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
||||
.preload(:author, :template_accesses)
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
@templates = Templates::Order.call(@templates, current_user, selected_order)
|
||||
TEMPLATES_PER_PAGE = 12
|
||||
FOLDERS_PER_PAGE = 18
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||
def show
|
||||
@templates = Template.active.accessible_by(current_ability)
|
||||
.where(folder: [@template_folder, *(params[:q].present? ? @template_folder.subfolders : [])])
|
||||
.preload(:author, :template_accesses)
|
||||
|
||||
@template_folders =
|
||||
@template_folder.subfolders.where(id: Template.accessible_by(current_ability).active.select(:folder_id))
|
||||
|
||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
|
||||
|
||||
if @templates.exists?
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
@templates = Templates::Order.call(@templates, current_user, selected_order)
|
||||
|
||||
limit =
|
||||
if @template_folders.size < 4
|
||||
TEMPLATES_PER_PAGE
|
||||
else
|
||||
(@template_folders.size < 7 ? 9 : 6)
|
||||
end
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit:)
|
||||
|
||||
load_related_submissions if params[:q].present? && @templates.blank?
|
||||
else
|
||||
@pagy, @template_folders = pagy(@template_folders, limit: FOLDERS_PER_PAGE)
|
||||
|
||||
@templates = @templates.none
|
||||
end
|
||||
end
|
||||
|
||||
def edit; end
|
||||
@@ -40,4 +66,21 @@ class TemplateFoldersController < ApplicationController
|
||||
def template_folder_params
|
||||
params.require(:template_folder).permit(:name)
|
||||
end
|
||||
|
||||
def load_related_submissions
|
||||
@related_submissions =
|
||||
Submission.accessible_by(current_ability)
|
||||
.where(archived_at: nil)
|
||||
.where(template_id: current_account.templates.active
|
||||
.where(folder: [@template_folder, *@template_folder.subfolders])
|
||||
.select(:id))
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,27 @@ class TemplatesArchivedController < ApplicationController
|
||||
load_and_authorize_resource :template, parent: false
|
||||
|
||||
def index
|
||||
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
|
||||
@templates = @templates.where.not(archived_at: nil)
|
||||
.preload(:author, :template_accesses, folder: :parent_folder)
|
||||
.order(id: :desc)
|
||||
|
||||
@templates = Templates.search(current_user, @templates, params[:q])
|
||||
|
||||
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
||||
|
||||
return unless params[:q].present? && @templates.blank?
|
||||
|
||||
@related_submissions =
|
||||
Submission.accessible_by(current_ability)
|
||||
.joins(:template)
|
||||
.where.not(templates: { archived_at: nil })
|
||||
.preload(:template_accesses, :created_by_user,
|
||||
template: :author,
|
||||
submitters: :start_form_submission_events)
|
||||
|
||||
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
|
||||
.order(id: :desc)
|
||||
|
||||
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,10 +11,11 @@ class TemplatesDashboardController < ApplicationController
|
||||
helper_method :selected_order
|
||||
|
||||
def index
|
||||
@template_folders = @template_folders.where(id: @templates.active.select(:folder_id))
|
||||
@template_folders =
|
||||
TemplateFolders.filter_active_folders(@template_folders.where(parent_folder_id: nil), @templates)
|
||||
|
||||
@template_folders = TemplateFolders.search(@template_folders, params[:q])
|
||||
@template_folders = sort_template_folders(@template_folders, current_user, selected_order)
|
||||
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
|
||||
|
||||
@pagy, @template_folders = pagy(
|
||||
@template_folders,
|
||||
@@ -68,40 +69,6 @@ class TemplatesDashboardController < ApplicationController
|
||||
Templates.search(current_user, rel, params[:q])
|
||||
end
|
||||
|
||||
def sort_template_folders(template_folders, current_user, order)
|
||||
case order
|
||||
when 'used_at'
|
||||
subquery =
|
||||
Template.left_joins(:submissions)
|
||||
.group(:folder_id)
|
||||
.where(account_id: current_user.account_id)
|
||||
.select(
|
||||
:folder_id,
|
||||
Template.arel_table[:updated_at].maximum.as('updated_at_max'),
|
||||
Submission.arel_table[:created_at].maximum.as('submission_created_at_max')
|
||||
)
|
||||
|
||||
template_folders = template_folders.joins(
|
||||
Template.arel_table
|
||||
.join(subquery.arel.as('templates'), Arel::Nodes::OuterJoin)
|
||||
.on(TemplateFolder.arel_table[:id].eq(Template.arel_table[:folder_id]))
|
||||
.join_sources
|
||||
)
|
||||
|
||||
template_folders.order(
|
||||
Arel::Nodes::Case.new
|
||||
.when(Template.arel_table[:submission_created_at_max].gt(Template.arel_table[:updated_at_max]))
|
||||
.then(Template.arel_table[:submission_created_at_max])
|
||||
.else(Template.arel_table[:updated_at_max])
|
||||
.desc
|
||||
)
|
||||
when 'name'
|
||||
template_folders.order(name: :asc)
|
||||
else
|
||||
template_folders.order(id: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def selected_order
|
||||
@selected_order ||=
|
||||
if cookies.permanent[:dashboard_templates_order].blank? ||
|
||||
|
||||
@@ -6,7 +6,9 @@ class TemplatesFoldersController < ApplicationController
|
||||
def edit; end
|
||||
|
||||
def update
|
||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
|
||||
name = [params[:parent_name], params[:name]].compact_blank.join(' / ')
|
||||
|
||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, name)
|
||||
|
||||
if @template.save
|
||||
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
|
||||
|
||||
@@ -15,6 +15,7 @@ export default targetable(class extends HTMLElement {
|
||||
static [target.static] = [
|
||||
'form',
|
||||
'fileDropzone',
|
||||
'folderDropzone',
|
||||
'fileDropzoneLoading'
|
||||
]
|
||||
|
||||
@@ -25,12 +26,13 @@ export default targetable(class extends HTMLElement {
|
||||
window.addEventListener('dragleave', this.onWindowDragleave)
|
||||
|
||||
this.fileDropzone?.addEventListener('drop', this.onDropFile)
|
||||
this.folderDropzone?.addEventListener('drop', this.onDropNewFolder)
|
||||
|
||||
this.folderCards.forEach((el) => el.addEventListener('drop', (e) => this.onDropFolder(e, el)))
|
||||
this.templateCards.forEach((el) => el.addEventListener('drop', this.onDropTemplate))
|
||||
this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart))
|
||||
|
||||
return [this.fileDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||
return [this.fileDropzone, this.folderDropzone, ...this.folderCards, ...this.templateCards].forEach((el) => {
|
||||
el?.addEventListener('dragover', this.onDragover)
|
||||
el?.addEventListener('dragleave', this.onDragleave)
|
||||
})
|
||||
@@ -46,6 +48,10 @@ export default targetable(class extends HTMLElement {
|
||||
onTemplateDragStart = (e) => {
|
||||
const id = e.target.href.split('/').pop()
|
||||
|
||||
this.folderCards.forEach((el) => el.classList.remove('bg-base-200', 'before:hidden'))
|
||||
this.folderDropzone?.classList?.remove('hidden')
|
||||
window.flash?.remove()
|
||||
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
|
||||
if (id) {
|
||||
@@ -104,7 +110,7 @@ export default targetable(class extends HTMLElement {
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('name', el.innerText.trim())
|
||||
formData.append('name', el.dataset.fullName)
|
||||
|
||||
fetch(`/templates/${templateId}/folder`, {
|
||||
method: 'PUT',
|
||||
@@ -176,6 +182,24 @@ export default targetable(class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
onDropNewFolder (e) {
|
||||
e.preventDefault()
|
||||
|
||||
const templateId = e.dataTransfer.getData('template_id')
|
||||
|
||||
const a = document.createElement('a')
|
||||
|
||||
a.href = `/templates/${templateId}/folder/edit?autocomplete=false`
|
||||
a.dataset.turboFrame = 'modal'
|
||||
a.classList.add('hidden')
|
||||
|
||||
document.body.append(a)
|
||||
|
||||
a.click()
|
||||
|
||||
a.remove()
|
||||
}
|
||||
|
||||
onDragleave () {
|
||||
this.style.backgroundColor = null
|
||||
|
||||
@@ -199,6 +223,7 @@ export default targetable(class extends HTMLElement {
|
||||
|
||||
this.isDrag = true
|
||||
|
||||
window.flash?.remove()
|
||||
this.fileDropzone?.classList?.remove('hidden')
|
||||
|
||||
this.hiddenOnDrag.forEach((el) => { el.style.display = 'none' })
|
||||
@@ -212,6 +237,7 @@ export default targetable(class extends HTMLElement {
|
||||
this.isDrag = false
|
||||
|
||||
this.fileDropzone?.classList?.add('hidden')
|
||||
this.folderDropzone?.classList?.add('hidden')
|
||||
|
||||
this.hiddenOnDrag.forEach((el) => { el.style.display = null })
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import autocomplete from 'autocompleter'
|
||||
|
||||
export default class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
if (this.dataset.enabled === 'false') return
|
||||
|
||||
autocomplete({
|
||||
input: this.input,
|
||||
preventSubmit: this.dataset.submitOnSelect === 'true' ? 0 : 1,
|
||||
@@ -14,12 +16,16 @@ export default class extends HTMLElement {
|
||||
}
|
||||
|
||||
onSelect = (item) => {
|
||||
this.input.value = item.name
|
||||
this.input.value = this.dataset.parentName ? item.name : item.full_name
|
||||
}
|
||||
|
||||
fetch = (text, resolve) => {
|
||||
const queryParams = new URLSearchParams({ q: text })
|
||||
|
||||
if (this.dataset.parentName) {
|
||||
queryParams.append('parent_name', this.dataset.parentName)
|
||||
}
|
||||
|
||||
fetch('/template_folders_autocomplete?' + queryParams).then(async (resp) => {
|
||||
const items = await resp.json()
|
||||
|
||||
@@ -34,7 +40,7 @@ export default class extends HTMLElement {
|
||||
|
||||
div.setAttribute('dir', 'auto')
|
||||
|
||||
div.textContent = item.name
|
||||
div.textContent = this.dataset.parentName ? item.name : item.full_name
|
||||
|
||||
return div
|
||||
}
|
||||
|
||||
@@ -72,12 +72,14 @@ class Template < ApplicationRecord
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
scope :archived, -> { where.not(archived_at: nil) }
|
||||
|
||||
delegate :name, to: :folder, prefix: true
|
||||
|
||||
def application_key
|
||||
external_id
|
||||
end
|
||||
|
||||
def folder_name
|
||||
folder.full_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def maybe_set_default_folder
|
||||
|
||||
@@ -4,36 +4,50 @@
|
||||
#
|
||||
# Table name: template_folders
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# name :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# author_id :bigint not null
|
||||
# id :bigint not null, primary key
|
||||
# archived_at :datetime
|
||||
# name :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# author_id :bigint not null
|
||||
# parent_folder_id :bigint
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_template_folders_on_account_id (account_id)
|
||||
# index_template_folders_on_author_id (author_id)
|
||||
# index_template_folders_on_account_id (account_id)
|
||||
# index_template_folders_on_author_id (author_id)
|
||||
# index_template_folders_on_parent_folder_id (parent_folder_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (author_id => users.id)
|
||||
# fk_rails_... (parent_folder_id => template_folders.id)
|
||||
#
|
||||
class TemplateFolder < ApplicationRecord
|
||||
DEFAULT_NAME = 'Default'
|
||||
|
||||
belongs_to :author, class_name: 'User'
|
||||
belongs_to :account
|
||||
belongs_to :parent_folder, class_name: 'TemplateFolder', optional: true
|
||||
|
||||
has_many :templates, dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
||||
has_many :subfolders, class_name: 'TemplateFolder', foreign_key: :parent_folder_id, inverse_of: :parent_folder,
|
||||
dependent: :destroy
|
||||
has_many :active_templates, -> { where(archived_at: nil) },
|
||||
class_name: 'Template', dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
||||
|
||||
scope :active, -> { where(archived_at: nil) }
|
||||
|
||||
def full_name
|
||||
if parent_folder_id?
|
||||
[parent_folder.name, name].join(' / ')
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
def default?
|
||||
name == DEFAULT_NAME
|
||||
end
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<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="M5 12l6 6" /><path d="M5 12l6 -6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1,3 @@
|
||||
<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="M12 21a9 9 0 1 0 0 -18a9 9 0 0 0 0 18" /><path d="M8 12l4 4" /><path d="M8 12h8" /><path d="M12 8l-4 4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1,3 @@
|
||||
<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="M12 21a9 9 0 1 0 0 -18a9 9 0 0 0 0 18" /><path d="M9 12l4 4" /><path d="M13 8l-4 4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 378 B |
@@ -0,0 +1,3 @@
|
||||
<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="M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5" /><path d="M16 19h6" /><path d="M19 16v6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
@@ -0,0 +1,3 @@
|
||||
<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="M17 5l-10 14" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -1,7 +1,7 @@
|
||||
<div class="block w-full md:w-52 flex-none">
|
||||
<menu-active>
|
||||
<ul id="account_settings_menu" class="menu px-0">
|
||||
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">← <%= t('back') %></a></li>
|
||||
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium flex items-center space-x-0.5"><%= svg_icon('arrow_left', class: 'w-4 h-4 stroke-2') %><span><%= t('back') %></span></a></li>
|
||||
<li class="menu-title py-0 !bg-transparent">
|
||||
<span class="!bg-transparent"><%= t('settings') %></span>
|
||||
</li>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||
<div>
|
||||
<%= link_to root_path do %>
|
||||
←
|
||||
<span><%= t('back_to_active') %></span>
|
||||
<%= link_to root_path, class: 'flex items-center' do %>
|
||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<div>
|
||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||
|
||||
@@ -10,6 +10,17 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:folder].present? %>
|
||||
<div class="tooltip tooltip-bottom 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-34 border-neutral-700" data-tip="<%= t('folder') %>">
|
||||
<%= link_to submissions_filter_path('folder', 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('folder', class: 'w-5 h-5 shrink-0') %>
|
||||
<span class="font-normal truncate"><%= params[:folder] %></span>
|
||||
<% end %>
|
||||
<%= link_to url_for(params.to_unsafe_h.except(:folder)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= svg_icon('x', class: 'w-5 h-5') %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:author].present? %>
|
||||
<div class="tooltip tooltip-bottom 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-34 border-neutral-700" data-tip="<%= t('author') %>">
|
||||
<%= 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 %>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% is_long = folder.name.size > 32 %>
|
||||
<a href="<%= folder_path(folder) %>" class="flex h-full flex-col justify-between rounded-2xl py-5 px-6 w-full bg-base-200 before:border-2 before:border-base-300 before:border-dashed before:absolute before:left-0 before:right-0 before:top-0 before:bottom-0 before:hidden before:rounded-2xl relative" data-targets="dashboard-dropzone.folderCards">
|
||||
<a href="<%= folder_path(folder) %>" class="flex h-full flex-col justify-between rounded-2xl py-5 px-6 w-full bg-base-200 before:border-2 before:border-base-300 before:border-dashed before:absolute before:left-0 before:right-0 before:top-0 before:bottom-0 before:hidden before:rounded-2xl relative" data-targets="dashboard-dropzone.folderCards" data-full-name="<%= folder.full_name %>">
|
||||
<% if !is_long %>
|
||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
<div>
|
||||
<%= link_to root_path do %>
|
||||
←
|
||||
<span><%= t('home') %></span>
|
||||
<%= link_to @template_folder.parent_folder ? folder_path(@template_folder.parent_folder) : root_path, class: 'flex items-center' do %>
|
||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||
<span style="margin-left: 3px"><%= @template_folder.parent_folder&.name || t('home') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<dashboard-dropzone>
|
||||
<div class="relative flex justify-between items-center w-full mb-4">
|
||||
<%= form_for '', url: '', id: form_id = SecureRandom.uuid, method: :post, class: 'hidden', data: { target: 'dashboard-dropzone.form' }, html: { enctype: 'multipart/form-data' } do %>
|
||||
<input name="form_id" value="<%= form_id %>">
|
||||
<input name="folder_name" value="<%= @template_folder.name %>">
|
||||
<input name="folder_name" value="<%= @template_folder.full_name %>">
|
||||
<button type="submit"></button>
|
||||
<input id="dashboard_dropzone_input" name="files[]" type="file" multiple>
|
||||
<% end %>
|
||||
<%= render 'templates/dashboard_dropzone', style: 'height: 137px' %>
|
||||
<% unless @template_folder.parent_folder %>
|
||||
<%= render 'templates/dashboard_folder_dropzone', style: 'height: 137px' %>
|
||||
<% end %>
|
||||
<h1 class="text-2xl truncate md:text-3xl font-bold flex items-center flex-grow min-w-0 space-x-2 md:flex <%= 'hidden' if params[:q].present? %>">
|
||||
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
|
||||
<span class="peer truncate">
|
||||
<%= @template_folder.name %>
|
||||
</span>
|
||||
<% if can?(:update, @template_folder) && @template_folder.name != TemplateFolder::DEFAULT_NAME %>
|
||||
<% if can?(:update, @template_folder) && @template_folder.full_name != TemplateFolder::DEFAULT_NAME %>
|
||||
<span class="opacity-0 hover:opacity-100 peer-hover:opacity-100">
|
||||
<a href="<%= edit_folder_path(@template_folder) %>" data-turbo-frame="modal">
|
||||
<%= svg_icon('pencil', class: 'w-7 h-7') %>
|
||||
@@ -27,18 +30,23 @@
|
||||
<% end %>
|
||||
</h1>
|
||||
<div class="flex space-x-2">
|
||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||
<% if params[:q].present? || @pagy.pages > 1 || @template_folders.present? %>
|
||||
<%= render 'shared/search_input' %>
|
||||
<% end %>
|
||||
<% if can?(:create, ::Template) %>
|
||||
<%= render 'templates/upload_button', folder_name: @template_folder.name %>
|
||||
<%= link_to new_template_path(folder_name: @template_folder.name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
||||
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
|
||||
<%= link_to new_template_path(folder_name: @template_folder.full_name), 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"><%= t('create') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% 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 @pagy.count.nil? || @pagy.count > 0 %>
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<%= render partial: 'templates/template', collection: @templates %>
|
||||
@@ -48,12 +56,21 @@
|
||||
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000 && !can?(:manage, :countless), selected_order: %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates', right_additional_html: templates_order_select_html %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: @templates.present? ? 'templates' : 'template_folders', right_additional_html: templates_order_select_html %>
|
||||
<% elsif params[:q].present? %>
|
||||
<div class="text-center">
|
||||
<div class="mt-16 text-3xl font-semibold">
|
||||
<%= t('templates_not_found') %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @related_submissions.present? %>
|
||||
<h1 class="text-2xl md:text-3xl sm:text-4xl font-bold mt-8 md:mt-4">
|
||||
<%= t('submissions') %>
|
||||
</h1>
|
||||
<div class="space-y-4 mt-4">
|
||||
<%= render partial: 'templates/submission', collection: @related_submissions, locals: { with_template: true } %>
|
||||
</div>
|
||||
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_path(q: params[:q], folder: @template_folder.full_name) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</dashboard-dropzone>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="absolute bottom-0 w-full cursor-pointer rounded-xl bg-base-100 border-2 border-base-300 border-dashed hidden z-50" data-target="dashboard-dropzone.folderDropzone" style="<%= local_assigns[:style] %>">
|
||||
<div class="absolute top-0 right-0 left-0 bottom-0 flex justify-center p-2 items-center pointer-events-none">
|
||||
<div class="flex flex-col items-center text-center" data-target="dashboard-dropzone.toggleLoading">
|
||||
<span class="flex flex-col items-center">
|
||||
<span>
|
||||
<%= svg_icon('folder_plus', class: 'w-9 h-9') %>
|
||||
</span>
|
||||
<div class="font-medium mb-1">
|
||||
<%= t('create_a_new_folder') %>
|
||||
</div>
|
||||
</span>
|
||||
<span class="flex flex-col items-center hidden" data-target="dashboard-dropzone.fileDropzoneLoading">
|
||||
<%= svg_icon('loader', class: 'w-9 h-9 animate-spin') %>
|
||||
<div class="font-medium mb-1">
|
||||
<%= t('loading') %>...
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="h-36 relative group">
|
||||
<a href="<%= template_path(template) %>" class="flex h-full flex-col justify-between rounded-2xl pt-6 px-7 w-full bg-base-200 before:border-2 before:border-base-300 before:border-dashed before:absolute before:left-0 before:right-0 before:top-0 before:bottom-0 before:hidden before:rounded-2xl" data-targets="dashboard-dropzone.templateCards">
|
||||
<div class="text-xl font-semibold" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<div class="text-xl font-semibold" style="line-height: 1.6rem; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
|
||||
<% if template.template_accesses.present? %>
|
||||
<%= svg_icon('lock', class: 'w-6 h-6 inline -translate-y-[4px]') %>
|
||||
<% end %>
|
||||
@@ -22,7 +22,7 @@
|
||||
<% if template.archived_at? %>
|
||||
<span class="flex items-center space-x-1 w-1/2">
|
||||
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
||||
<span class="truncate"><%= template.folder.name %></span>
|
||||
<span class="truncate"><%= template.folder.full_name %></span>
|
||||
</span>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
||||
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||
<span class="text-sm">
|
||||
<%= @template.folder.name %>
|
||||
<%= @template.folder.full_name %>
|
||||
</span>
|
||||
</a>
|
||||
<% if can?(:update, template) %>
|
||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="<%= t('move') %>">
|
||||
<a href="<%= edit_template_folder_path(template.id) %>" data-turbo-frame="modal">
|
||||
<a href="<%= edit_template_folder_path(template.id, subfolder: @template.folder.parent_folder.present?) %>" data-turbo-frame="modal">
|
||||
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||
</a>
|
||||
<folder-autocomplete class="flex justify-between w-full">
|
||||
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.full_name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="shrink-0 link peer-focus:hidden mr-1.5">
|
||||
<%= t('change_folder') %>
|
||||
</a>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div>
|
||||
<%= link_to root_path do %>
|
||||
←
|
||||
<span><%= t('back_to_active') %></span>
|
||||
<%= link_to root_path, class: 'flex items-center' do %>
|
||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex justify-between mb-4 items-center">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('document_templates_html') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
<h1 class="text-2xl md:text-3xl font-bold md:block <%= 'hidden' if params[:q].present? %>"><%= t('document_templates_html') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||
@@ -22,5 +22,14 @@
|
||||
<%= t('templates_not_found') %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @related_submissions.present? %>
|
||||
<h1 class="text-2xl md:text-3xl sm:text-4xl font-bold mt-8 md:mt-4">
|
||||
<%= t('submissions') %>
|
||||
</h1>
|
||||
<div class="space-y-4 mt-4">
|
||||
<%= render partial: 'templates/submission', collection: @related_submissions, locals: { with_template: true } %>
|
||||
</div>
|
||||
<%= render 'shared/pagination', pagy: @related_submissions_pagy, items_name: 'submissions', next_page_path: submissions_archived_index_path(q: params[:q]) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates' %>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<% 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 %>
|
||||
<div>
|
||||
<%= link_to template_path(@template) do %>
|
||||
←
|
||||
<span><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row md:items-center mb-6 gap-3">
|
||||
<div class="flex w-full justify-between md:items-end items-center">
|
||||
<div class="flex w-full justify-between md:items-end items-end">
|
||||
<div>
|
||||
<div>
|
||||
<%= link_to template_path(@template), class: 'flex items-center' do %>
|
||||
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold md:block"><%= t('submissions') %> <span class="badge badge-outline badge-lg align-middle"><%= t('archived') %></span></h1>
|
||||
</div>
|
||||
<div class="flex space-x-2 justify-end">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<% unless show_dropzone %>
|
||||
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
|
||||
<% end %>
|
||||
<%= render 'templates/dashboard_folder_dropzone', style: 'height: 114px' %>
|
||||
<div class="flex items-center flex-grow min-w-0">
|
||||
<% if has_archived || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
||||
<div class="mr-2">
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
<% with_subfolder = @template.folder.name != TemplateFolder::DEFAULT_NAME && params[:subfolder] != 'false' %>
|
||||
<%= render 'shared/turbo_modal', title: t('move_into_folder') do %>
|
||||
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||
<div class="form-control my-6">
|
||||
<folder-autocomplete class="block" data-submit-on-select="true">
|
||||
<% if with_subfolder %>
|
||||
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off } do |f| %>
|
||||
<%= f.hidden_field :parent_name, value: @template.folder.parent_folder&.name || @template.folder.name %>
|
||||
<toggle-visible data-element-ids="<%= %w[folder_form subfolder_form].to_json %>" class="block relative">
|
||||
<div class="flex items-center justify-between mb-2.5">
|
||||
<label for="is_root_folder" class="flex items-center">
|
||||
<%= check_box_tag :is_root_folder, 'folder_form', data: { action: 'change:toggle-visible#trigger' }, class: 'hidden' %>
|
||||
<span class="flex items-center space-x-0.5 mt-1 peer">
|
||||
<%= svg_icon('folder', class: 'w-6 h-5 flex-shrink-0') %>
|
||||
<span class="text-md">
|
||||
<%= @template.folder.parent_folder&.name || @template.folder.name %>
|
||||
</span>
|
||||
</span>
|
||||
<span class="pl-1 tooltip tooltip-right md:opacity-0 hover:opacity-100 peer-hover:opacity-100" data-tip="<%= t('change_parent_folder') %>">
|
||||
<span href="<%= edit_template_folder_path(@template.id) %>" data-turbo-frame="modal">
|
||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</toggle-visible>
|
||||
<div class="form-control mb-6">
|
||||
<folder-autocomplete class="block" data-submit-on-select="true" data-parent-name="<%= @template.folder.parent_folder&.name || @template.folder.name %>" data-enabled="<%= params[:autocomplete] != 'false' %>">
|
||||
<%= f.text_field :name, required: true, placeholder: "#{t('new_subfolder_name')}...", class: 'base-input w-full', autofocus: true %>
|
||||
</folder-autocomplete>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= f.button button_title(title: t('move'), disabled_with: t('moving')), class: 'base-button' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'folder_form', autocomplete: :off, class: "mt-6 #{'hidden' if with_subfolder}" } do |f| %>
|
||||
<div class="form-control mb-6">
|
||||
<folder-autocomplete class="block" data-submit-on-select="true" data-enabled="<%= params[:autocomplete] != 'false' %>">
|
||||
<%= f.text_field :name, required: true, placeholder: "#{t('new_folder_name')}...", class: 'base-input w-full', autofocus: true %>
|
||||
</folder-autocomplete>
|
||||
</div>
|
||||
|
||||
@@ -460,6 +460,10 @@ en: &en
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: '%{users_count}/%{total_users_count} Pro users limit was reached. To invite additional users, please purchase more Pro user seats via the "Manage plan" button.'
|
||||
move_into_folder: Move Into Folder
|
||||
new_folder_name: New Folder Name
|
||||
new_subfolder_name: New Subfolder Name
|
||||
change_parent_folder: Change Parent Folder
|
||||
folder: Folder
|
||||
create_a_new_folder: Create a New Folder
|
||||
exit_preview: Exit Preview
|
||||
general: General
|
||||
recipients: Recipients
|
||||
@@ -1326,6 +1330,10 @@ es: &es
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Se alcanzó el límite de %{users_count}/%{total_users_count} usuarios Pro. Para invitar a más usuarios, compra más plazas Pro usando el botón "Gestionar plan".'
|
||||
move_into_folder: Mover a la carpeta
|
||||
new_folder_name: Nuevo nombre de la carpeta
|
||||
new_subfolder_name: Nuevo nombre de subcarpeta
|
||||
change_parent_folder: Cambiar carpeta principal
|
||||
folder: Carpeta
|
||||
create_a_new_folder: Crear una nueva carpeta
|
||||
exit_preview: Salir de la vista previa
|
||||
general: General
|
||||
recipients: Destinatarios
|
||||
@@ -2191,6 +2199,10 @@ it: &it
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'È stato raggiunto il limite di %{users_count}/%{total_users_count} utenti Pro. Per invitare altri utenti, acquista più posti Pro tramite il pulsante "Gestisci piano".'
|
||||
move_into_folder: Sposta nella cartella
|
||||
new_folder_name: Nuovo nome della cartella
|
||||
new_subfolder_name: Nuovo nome della sottocartella
|
||||
change_parent_folder: Cambia cartella principale
|
||||
folder: Cartella
|
||||
create_a_new_folder: Crea una nuova cartella
|
||||
exit_preview: "Esci dall'anteprima"
|
||||
general: Generale
|
||||
recipients: Destinatari
|
||||
@@ -3058,6 +3070,10 @@ fr: &fr
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'La limite de %{users_count}/%{total_users_count} utilisateurs Pro a été atteinte. Pour inviter d''autres utilisateurs, veuillez acheter plus de places Pro via le bouton "Gérer le plan".'
|
||||
move_into_folder: Déplacer dans le dossier
|
||||
new_folder_name: Nouveau nom du dossier
|
||||
new_subfolder_name: Nouveau nom du sous-dossier
|
||||
change_parent_folder: Changer le dossier parent
|
||||
folder: Dossier
|
||||
create_a_new_folder: Créer un nouveau dossier
|
||||
exit_preview: "Quitter l'aperçu"
|
||||
general: Général
|
||||
recipients: Destinataires
|
||||
@@ -3925,6 +3941,10 @@ pt: &pt
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'O limite de %{users_count}/%{total_users_count} usuários Pro foi atingido. Para convidar mais usuários, adquira mais licenças Pro através do botão "Gerenciar plano".'
|
||||
move_into_folder: Mover para pasta
|
||||
new_folder_name: Novo nome da pasta
|
||||
new_subfolder_name: Novo nome da subpasta
|
||||
change_parent_folder: Alterar pasta pai
|
||||
folder: Pasta
|
||||
create_a_new_folder: Criar uma nova pasta
|
||||
exit_preview: Sair da pré-visualização
|
||||
general: Geral
|
||||
recipients: Destinatários
|
||||
@@ -4792,6 +4812,10 @@ de: &de
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Das Limit von %{users_count}/%{total_users_count} Pro-Benutzern wurde erreicht. Um weitere Benutzer einzuladen, kaufen Sie bitte zusätzliche Pro-Benutzerplätze über die Schaltfläche "Plan verwalten".'
|
||||
move_into_folder: In Ordner verschieben
|
||||
new_folder_name: Neuer Ordnername
|
||||
new_subfolder_name: Neuer Unterordnername
|
||||
change_parent_folder: Übergeordneten Ordner ändern
|
||||
folder: Ordner
|
||||
create_a_new_folder: Neuen Ordner erstellen
|
||||
exit_preview: Vorschau beenden
|
||||
general: Allgemein
|
||||
recipients: Empfänger
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddParentFolderIdToTemplateFolders < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_reference :template_folders, :parent_folder, foreign_key: { to_table: :template_folders }, index: true
|
||||
end
|
||||
end
|
||||
+4
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_06_27_130628) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_07_18_121133) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gin"
|
||||
enable_extension "plpgsql"
|
||||
@@ -356,8 +356,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_130628) do
|
||||
t.datetime "archived_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "parent_folder_id"
|
||||
t.index ["account_id"], name: "index_template_folders_on_account_id"
|
||||
t.index ["author_id"], name: "index_template_folders_on_author_id"
|
||||
t.index ["parent_folder_id"], name: "index_template_folders_on_parent_folder_id"
|
||||
end
|
||||
|
||||
create_table "template_sharings", force: :cascade do |t|
|
||||
@@ -498,6 +500,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_130628) do
|
||||
add_foreign_key "submitters", "submissions"
|
||||
add_foreign_key "template_accesses", "templates"
|
||||
add_foreign_key "template_folders", "accounts"
|
||||
add_foreign_key "template_folders", "template_folders", column: "parent_folder_id"
|
||||
add_foreign_key "template_folders", "users", column: "author_id"
|
||||
add_foreign_key "template_sharings", "templates"
|
||||
add_foreign_key "templates", "accounts"
|
||||
|
||||
@@ -5,6 +5,7 @@ module Submissions
|
||||
ALLOWED_PARAMS = %w[
|
||||
author
|
||||
status
|
||||
folder
|
||||
completed_at_from
|
||||
completed_at_to
|
||||
created_at_from
|
||||
@@ -24,6 +25,7 @@ module Submissions
|
||||
filters = normalize_filter_params(params, current_user)
|
||||
|
||||
submissions = filter_by_author(submissions, filters, current_user)
|
||||
submissions = filter_by_folder(submissions, filters, current_user)
|
||||
submissions = filter_by_status(submissions, filters)
|
||||
submissions = filter_by_created_at(submissions, filters)
|
||||
|
||||
@@ -34,6 +36,7 @@ module Submissions
|
||||
return submissions if filters[:author].blank?
|
||||
|
||||
user = current_user.account.users.find_by(email: filters[:author])
|
||||
|
||||
submissions.where(created_by_user_id: user&.id || -1)
|
||||
end
|
||||
|
||||
@@ -87,6 +90,17 @@ module Submissions
|
||||
submissions
|
||||
end
|
||||
|
||||
def filter_by_folder(submissions, filters, current_user)
|
||||
return submissions if filters[:folder].blank?
|
||||
|
||||
folders =
|
||||
TemplateFolders.filter_by_full_name(current_user.account.template_folders, filters[:folder])
|
||||
|
||||
folders += folders.preload(:subfolders).flat_map(&:subfolders)
|
||||
|
||||
submissions.joins(:template).where(templates: { folder_id: folders.map(&:id) })
|
||||
end
|
||||
|
||||
def filter_by_completed_at(submissions, filters)
|
||||
return submissions unless filters[:completed_at_from].present? || filters[:completed_at_to].present?
|
||||
|
||||
|
||||
+73
-1
@@ -3,15 +3,87 @@
|
||||
module TemplateFolders
|
||||
module_function
|
||||
|
||||
def filter_by_full_name(template_folders, name)
|
||||
parent_name, name = name.split(' / ', 2).map(&:squish)
|
||||
|
||||
if name.present?
|
||||
parent_folder = template_folders.where(parent_folder_id: nil).find_by(name: parent_name)
|
||||
else
|
||||
name = parent_name
|
||||
end
|
||||
|
||||
template_folders.where(name:, parent_folder:)
|
||||
end
|
||||
|
||||
def search(folders, keyword)
|
||||
return folders if keyword.blank?
|
||||
|
||||
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||
end
|
||||
|
||||
def filter_active_folders(template_folders, templates)
|
||||
folder_exists =
|
||||
templates.active.where(TemplateFolder.arel_table[:id].eq(Template.arel_table[:folder_id]))
|
||||
.select(1).limit(1).arel.exists
|
||||
|
||||
subfolders_arel = TemplateFolder.arel_table.alias('subfolders')
|
||||
|
||||
subfolder_exists =
|
||||
TemplateFolder.from(subfolders_arel)
|
||||
.where(subfolders_arel[:parent_folder_id].eq(TemplateFolder.arel_table[:id]))
|
||||
.where(
|
||||
templates.active.where(Template.arel_table[:folder_id].eq(subfolders_arel[:id])).arel.exists
|
||||
).select(1).limit(1).arel.exists
|
||||
|
||||
template_folders.where(folder_exists).or(template_folders.where(subfolder_exists))
|
||||
end
|
||||
|
||||
def sort(template_folders, current_user, order)
|
||||
case order
|
||||
when 'used_at'
|
||||
subquery =
|
||||
Template.left_joins(:submissions)
|
||||
.group(:folder_id)
|
||||
.where(account_id: current_user.account_id)
|
||||
.select(
|
||||
:folder_id,
|
||||
Template.arel_table[:updated_at].maximum.as('updated_at_max'),
|
||||
Submission.arel_table[:created_at].maximum.as('submission_created_at_max')
|
||||
)
|
||||
|
||||
template_folders = template_folders.joins(
|
||||
Template.arel_table
|
||||
.join(subquery.arel.as('templates'), Arel::Nodes::OuterJoin)
|
||||
.on(TemplateFolder.arel_table[:id].eq(Template.arel_table[:folder_id]))
|
||||
.join_sources
|
||||
)
|
||||
|
||||
template_folders.order(
|
||||
Arel::Nodes::Case.new
|
||||
.when(Template.arel_table[:submission_created_at_max].gt(Template.arel_table[:updated_at_max]))
|
||||
.then(Template.arel_table[:submission_created_at_max])
|
||||
.else(Template.arel_table[:updated_at_max])
|
||||
.desc
|
||||
)
|
||||
when 'name'
|
||||
template_folders.order(name: :asc)
|
||||
else
|
||||
template_folders.order(id: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_create_by_name(author, name)
|
||||
return author.account.default_template_folder if name.blank? || name == TemplateFolder::DEFAULT_NAME
|
||||
|
||||
author.account.template_folders.create_with(author:, account: author.account).find_or_create_by(name:)
|
||||
parent_name, name = name.split(' / ', 2).map(&:squish)
|
||||
|
||||
if name.present?
|
||||
parent_folder = author.account.template_folders.create_with(author:)
|
||||
.find_or_create_by(name: parent_name, parent_folder_id: nil)
|
||||
else
|
||||
name = parent_name
|
||||
end
|
||||
|
||||
author.account.template_folders.create_with(author:).find_or_create_by(name:, parent_folder:)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user