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?
|
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
|
||||||
|
|
||||||
submitters = paginate(
|
submitters = paginate(
|
||||||
submitters.preload(template: :folder, submission: [:submitters, { audit_trail_attachment: :blob,
|
submitters.preload(template: { folder: :parent_folder },
|
||||||
combined_document_attachment: :blob }],
|
submission: [:submitters, { audit_trail_attachment: :blob,
|
||||||
|
combined_document_attachment: :blob }],
|
||||||
documents_attachments: :blob, attachments_attachments: :blob),
|
documents_attachments: :blob, attachments_attachments: :blob),
|
||||||
field: :completed_at
|
field: :completed_at
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module Api
|
|||||||
submissions = filter_submissions(submissions, params)
|
submissions = filter_submissions(submissions, params)
|
||||||
|
|
||||||
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
submissions = paginate(submissions.preload(:created_by_user, :submitters,
|
||||||
template: :folder,
|
template: { folder: :parent_folder },
|
||||||
combined_document_attachment: :blob,
|
combined_document_attachment: :blob,
|
||||||
audit_trail_attachment: :blob))
|
audit_trail_attachment: :blob))
|
||||||
|
|
||||||
@@ -104,9 +104,10 @@ module Api
|
|||||||
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
|
||||||
if params[:template_folder].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
|
end
|
||||||
|
|
||||||
if params.key?(:archived)
|
if params.key?(:archived)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module Api
|
|||||||
def index
|
def index
|
||||||
templates = filter_templates(@templates, params)
|
templates = filter_templates(@templates, params)
|
||||||
|
|
||||||
templates = paginate(templates.preload(:author, :folder))
|
templates = paginate(templates.preload(:author, folder: :parent_folder))
|
||||||
|
|
||||||
schema_documents =
|
schema_documents =
|
||||||
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
|
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
|
||||||
@@ -92,9 +92,9 @@ module Api
|
|||||||
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
templates = templates.where(slug: params[:slug]) if params[:slug].present?
|
||||||
|
|
||||||
if params[:folder].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
|
end
|
||||||
|
|
||||||
templates
|
templates
|
||||||
|
|||||||
@@ -3,14 +3,31 @@
|
|||||||
class TemplateFoldersAutocompleteController < ApplicationController
|
class TemplateFoldersAutocompleteController < ApplicationController
|
||||||
load_and_authorize_resource :template_folder, parent: false
|
load_and_authorize_resource :template_folder, parent: false
|
||||||
|
|
||||||
LIMIT = 100
|
LIMIT = 30
|
||||||
|
|
||||||
def index
|
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))
|
if name
|
||||||
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,13 +5,39 @@ class TemplateFoldersController < ApplicationController
|
|||||||
|
|
||||||
helper_method :selected_order
|
helper_method :selected_order
|
||||||
|
|
||||||
def show
|
TEMPLATES_PER_PAGE = 12
|
||||||
@templates = @template_folder.templates.active.accessible_by(current_ability)
|
FOLDERS_PER_PAGE = 18
|
||||||
.preload(:author, :template_accesses)
|
|
||||||
@templates = Templates.search(current_user, @templates, params[:q])
|
|
||||||
@templates = Templates::Order.call(@templates, current_user, selected_order)
|
|
||||||
|
|
||||||
@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
|
end
|
||||||
|
|
||||||
def edit; end
|
def edit; end
|
||||||
@@ -40,4 +66,21 @@ class TemplateFoldersController < ApplicationController
|
|||||||
def template_folder_params
|
def template_folder_params
|
||||||
params.require(:template_folder).permit(:name)
|
params.require(:template_folder).permit(:name)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -4,9 +4,27 @@ class TemplatesArchivedController < ApplicationController
|
|||||||
load_and_authorize_resource :template, parent: false
|
load_and_authorize_resource :template, parent: false
|
||||||
|
|
||||||
def index
|
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])
|
@templates = Templates.search(current_user, @templates, params[:q])
|
||||||
|
|
||||||
@pagy, @templates = pagy_auto(@templates, limit: 12)
|
@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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
helper_method :selected_order
|
helper_method :selected_order
|
||||||
|
|
||||||
def index
|
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 = 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(
|
@pagy, @template_folders = pagy(
|
||||||
@template_folders,
|
@template_folders,
|
||||||
@@ -68,40 +69,6 @@ class TemplatesDashboardController < ApplicationController
|
|||||||
Templates.search(current_user, rel, params[:q])
|
Templates.search(current_user, rel, params[:q])
|
||||||
end
|
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
|
def selected_order
|
||||||
@selected_order ||=
|
@selected_order ||=
|
||||||
if cookies.permanent[:dashboard_templates_order].blank? ||
|
if cookies.permanent[:dashboard_templates_order].blank? ||
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ class TemplatesFoldersController < ApplicationController
|
|||||||
def edit; end
|
def edit; end
|
||||||
|
|
||||||
def update
|
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
|
if @template.save
|
||||||
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
|
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] = [
|
static [target.static] = [
|
||||||
'form',
|
'form',
|
||||||
'fileDropzone',
|
'fileDropzone',
|
||||||
|
'folderDropzone',
|
||||||
'fileDropzoneLoading'
|
'fileDropzoneLoading'
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -25,12 +26,13 @@ export default targetable(class extends HTMLElement {
|
|||||||
window.addEventListener('dragleave', this.onWindowDragleave)
|
window.addEventListener('dragleave', this.onWindowDragleave)
|
||||||
|
|
||||||
this.fileDropzone?.addEventListener('drop', this.onDropFile)
|
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.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('drop', this.onDropTemplate))
|
||||||
this.templateCards.forEach((el) => el.addEventListener('dragstart', this.onTemplateDragStart))
|
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('dragover', this.onDragover)
|
||||||
el?.addEventListener('dragleave', this.onDragleave)
|
el?.addEventListener('dragleave', this.onDragleave)
|
||||||
})
|
})
|
||||||
@@ -46,6 +48,10 @@ export default targetable(class extends HTMLElement {
|
|||||||
onTemplateDragStart = (e) => {
|
onTemplateDragStart = (e) => {
|
||||||
const id = e.target.href.split('/').pop()
|
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'
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
@@ -104,7 +110,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
} else {
|
} else {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
formData.append('name', el.innerText.trim())
|
formData.append('name', el.dataset.fullName)
|
||||||
|
|
||||||
fetch(`/templates/${templateId}/folder`, {
|
fetch(`/templates/${templateId}/folder`, {
|
||||||
method: 'PUT',
|
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 () {
|
onDragleave () {
|
||||||
this.style.backgroundColor = null
|
this.style.backgroundColor = null
|
||||||
|
|
||||||
@@ -199,6 +223,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
|
|
||||||
this.isDrag = true
|
this.isDrag = true
|
||||||
|
|
||||||
|
window.flash?.remove()
|
||||||
this.fileDropzone?.classList?.remove('hidden')
|
this.fileDropzone?.classList?.remove('hidden')
|
||||||
|
|
||||||
this.hiddenOnDrag.forEach((el) => { el.style.display = 'none' })
|
this.hiddenOnDrag.forEach((el) => { el.style.display = 'none' })
|
||||||
@@ -212,6 +237,7 @@ export default targetable(class extends HTMLElement {
|
|||||||
this.isDrag = false
|
this.isDrag = false
|
||||||
|
|
||||||
this.fileDropzone?.classList?.add('hidden')
|
this.fileDropzone?.classList?.add('hidden')
|
||||||
|
this.folderDropzone?.classList?.add('hidden')
|
||||||
|
|
||||||
this.hiddenOnDrag.forEach((el) => { el.style.display = null })
|
this.hiddenOnDrag.forEach((el) => { el.style.display = null })
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import autocomplete from 'autocompleter'
|
|||||||
|
|
||||||
export default class extends HTMLElement {
|
export default class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
if (this.dataset.enabled === 'false') return
|
||||||
|
|
||||||
autocomplete({
|
autocomplete({
|
||||||
input: this.input,
|
input: this.input,
|
||||||
preventSubmit: this.dataset.submitOnSelect === 'true' ? 0 : 1,
|
preventSubmit: this.dataset.submitOnSelect === 'true' ? 0 : 1,
|
||||||
@@ -14,12 +16,16 @@ export default class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSelect = (item) => {
|
onSelect = (item) => {
|
||||||
this.input.value = item.name
|
this.input.value = this.dataset.parentName ? item.name : item.full_name
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch = (text, resolve) => {
|
fetch = (text, resolve) => {
|
||||||
const queryParams = new URLSearchParams({ q: text })
|
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) => {
|
fetch('/template_folders_autocomplete?' + queryParams).then(async (resp) => {
|
||||||
const items = await resp.json()
|
const items = await resp.json()
|
||||||
|
|
||||||
@@ -34,7 +40,7 @@ export default class extends HTMLElement {
|
|||||||
|
|
||||||
div.setAttribute('dir', 'auto')
|
div.setAttribute('dir', 'auto')
|
||||||
|
|
||||||
div.textContent = item.name
|
div.textContent = this.dataset.parentName ? item.name : item.full_name
|
||||||
|
|
||||||
return div
|
return div
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,14 @@ class Template < ApplicationRecord
|
|||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
scope :archived, -> { where.not(archived_at: nil) }
|
scope :archived, -> { where.not(archived_at: nil) }
|
||||||
|
|
||||||
delegate :name, to: :folder, prefix: true
|
|
||||||
|
|
||||||
def application_key
|
def application_key
|
||||||
external_id
|
external_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def folder_name
|
||||||
|
folder.full_name
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def maybe_set_default_folder
|
def maybe_set_default_folder
|
||||||
|
|||||||
@@ -4,36 +4,50 @@
|
|||||||
#
|
#
|
||||||
# Table name: template_folders
|
# Table name: template_folders
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# archived_at :datetime
|
# archived_at :datetime
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# account_id :bigint not null
|
# account_id :bigint not null
|
||||||
# author_id :bigint not null
|
# author_id :bigint not null
|
||||||
|
# parent_folder_id :bigint
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_template_folders_on_account_id (account_id)
|
# index_template_folders_on_account_id (account_id)
|
||||||
# index_template_folders_on_author_id (author_id)
|
# index_template_folders_on_author_id (author_id)
|
||||||
|
# index_template_folders_on_parent_folder_id (parent_folder_id)
|
||||||
#
|
#
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
#
|
#
|
||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
# fk_rails_... (author_id => users.id)
|
# fk_rails_... (author_id => users.id)
|
||||||
|
# fk_rails_... (parent_folder_id => template_folders.id)
|
||||||
#
|
#
|
||||||
class TemplateFolder < ApplicationRecord
|
class TemplateFolder < ApplicationRecord
|
||||||
DEFAULT_NAME = 'Default'
|
DEFAULT_NAME = 'Default'
|
||||||
|
|
||||||
belongs_to :author, class_name: 'User'
|
belongs_to :author, class_name: 'User'
|
||||||
belongs_to :account
|
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 :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) },
|
has_many :active_templates, -> { where(archived_at: nil) },
|
||||||
class_name: 'Template', dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
class_name: 'Template', dependent: :destroy, foreign_key: :folder_id, inverse_of: :folder
|
||||||
|
|
||||||
scope :active, -> { where(archived_at: nil) }
|
scope :active, -> { where(archived_at: nil) }
|
||||||
|
|
||||||
|
def full_name
|
||||||
|
if parent_folder_id?
|
||||||
|
[parent_folder.name, name].join(' / ')
|
||||||
|
else
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def default?
|
def default?
|
||||||
name == DEFAULT_NAME
|
name == DEFAULT_NAME
|
||||||
end
|
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">
|
<div class="block w-full md:w-52 flex-none">
|
||||||
<menu-active>
|
<menu-active>
|
||||||
<ul id="account_settings_menu" class="menu px-0">
|
<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">
|
<li class="menu-title py-0 !bg-transparent">
|
||||||
<span class="!bg-transparent"><%= t('settings') %></span>
|
<span class="!bg-transparent"><%= t('settings') %></span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<div>
|
<div>
|
||||||
<%= link_to root_path do %>
|
<%= link_to root_path, class: 'flex items-center' do %>
|
||||||
←
|
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||||
<span><%= t('back_to_active') %></span>
|
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row md:items-center mb-4 gap-3">
|
<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>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
<% if params[:q].present? || @pagy.pages > 1 || filter_params.present? %>
|
||||||
|
|||||||
@@ -10,6 +10,17 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% 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? %>
|
<% 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') %>">
|
<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 %>
|
<%= 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 %>
|
<% 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 %>
|
<% if !is_long %>
|
||||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= link_to root_path do %>
|
<%= 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><%= t('home') %></span>
|
<span style="margin-left: 3px"><%= @template_folder.parent_folder&.name || t('home') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<dashboard-dropzone>
|
<dashboard-dropzone>
|
||||||
<div class="relative flex justify-between items-center w-full mb-4">
|
<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 %>
|
<%= 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="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>
|
<button type="submit"></button>
|
||||||
<input id="dashboard_dropzone_input" name="files[]" type="file" multiple>
|
<input id="dashboard_dropzone_input" name="files[]" type="file" multiple>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'templates/dashboard_dropzone', style: 'height: 137px' %>
|
<%= 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? %>">
|
<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') %>
|
<%= svg_icon('folder', class: 'w-9 h-9 flex-shrink-0') %>
|
||||||
<span class="peer truncate">
|
<span class="peer truncate">
|
||||||
<%= @template_folder.name %>
|
<%= @template_folder.name %>
|
||||||
</span>
|
</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">
|
<span class="opacity-0 hover:opacity-100 peer-hover:opacity-100">
|
||||||
<a href="<%= edit_folder_path(@template_folder) %>" data-turbo-frame="modal">
|
<a href="<%= edit_folder_path(@template_folder) %>" data-turbo-frame="modal">
|
||||||
<%= svg_icon('pencil', class: 'w-7 h-7') %>
|
<%= svg_icon('pencil', class: 'w-7 h-7') %>
|
||||||
@@ -27,18 +30,23 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex space-x-2">
|
<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' %>
|
<%= render 'shared/search_input' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, ::Template) %>
|
<% if can?(:create, ::Template) %>
|
||||||
<%= render 'templates/upload_button', folder_name: @template_folder.name %>
|
<%= render 'templates/upload_button', folder_name: @template_folder.full_name %>
|
||||||
<%= link_to new_template_path(folder_name: @template_folder.name), class: 'white-button !border gap-2', data: { turbo_frame: :modal } do %>
|
<%= 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') %>
|
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
|
||||||
<span class="hidden md:block"><%= t('create') %></span>
|
<span class="hidden md:block"><%= t('create') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</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 %>
|
<% if @pagy.count.nil? || @pagy.count > 0 %>
|
||||||
<div class="grid gap-4 md:grid-cols-3">
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
<%= render partial: 'templates/template', collection: @templates %>
|
<%= 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: %>
|
<%= render 'shared/templates_order_select', with_recently_used: @pagy.count.present? && @pagy.count < 10_000 && !can?(:manage, :countless), selected_order: %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% 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? %>
|
<% elsif params[:q].present? %>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="mt-16 text-3xl font-semibold">
|
<div class="mt-16 text-3xl font-semibold">
|
||||||
<%= t('templates_not_found') %>
|
<%= t('templates_not_found') %>
|
||||||
</div>
|
</div>
|
||||||
</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 %>
|
<% end %>
|
||||||
</dashboard-dropzone>
|
</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">
|
<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">
|
<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? %>
|
<% if template.template_accesses.present? %>
|
||||||
<%= svg_icon('lock', class: 'w-6 h-6 inline -translate-y-[4px]') %>
|
<%= svg_icon('lock', class: 'w-6 h-6 inline -translate-y-[4px]') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<% if template.archived_at? %>
|
<% if template.archived_at? %>
|
||||||
<span class="flex items-center space-x-1 w-1/2">
|
<span class="flex items-center space-x-1 w-1/2">
|
||||||
<%= svg_icon('folder', class: 'w-4 h-4 flex-shrink-0') %>
|
<%= 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>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -16,12 +16,12 @@
|
|||||||
<a href="<%= folder_path(@template.folder) %>" class="flex items-center space-x-1 mt-1 peer">
|
<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') %>
|
<%= svg_icon('folder', class: 'w-5 h-5 flex-shrink-0') %>
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
<%= @template.folder.name %>
|
<%= @template.folder.full_name %>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<% if can?(:update, template) %>
|
<% 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') %>">
|
<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') %>
|
<%= svg_icon('pencil_share', class: 'w-5 h-5') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||||
</a>
|
</a>
|
||||||
<folder-autocomplete class="flex justify-between w-full">
|
<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">
|
<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') %>
|
<%= t('change_folder') %>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= link_to root_path do %>
|
<%= link_to root_path, class: 'flex items-center' do %>
|
||||||
←
|
<%= svg_icon('chevron_left', class: 'w-5 h-5') %>
|
||||||
<span><%= t('back_to_active') %></span>
|
<span style="margin-left: 3px"><%= t('back_to_active') %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mb-4 items-center">
|
<div class="flex justify-between mb-4 items-center">
|
||||||
<div>
|
<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>
|
</div>
|
||||||
<% if params[:q].present? || @pagy.pages > 1 %>
|
<% if params[:q].present? || @pagy.pages > 1 %>
|
||||||
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
<%= render 'shared/search_input', placeholder: "#{t('search')}..." %>
|
||||||
@@ -22,5 +22,14 @@
|
|||||||
<%= t('templates_not_found') %>
|
<%= t('templates_not_found') %>
|
||||||
</div>
|
</div>
|
||||||
</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 %>
|
<% end %>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates' %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'templates' %>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
|
||||||
<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %>
|
<% with_filters = @pagy.pages > 1 || params[:q].present? || filter_params.present? %>
|
||||||
<%= render 'templates/title', template: @template %>
|
<%= 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 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>
|
||||||
|
<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>
|
<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>
|
||||||
<div class="flex space-x-2 justify-end">
|
<div class="flex space-x-2 justify-end">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<% unless show_dropzone %>
|
<% unless show_dropzone %>
|
||||||
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
|
<%= render 'templates/dashboard_dropzone', style: 'height: 114px' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= render 'templates/dashboard_folder_dropzone', style: 'height: 114px' %>
|
||||||
<div class="flex items-center flex-grow min-w-0">
|
<div class="flex items-center flex-grow min-w-0">
|
||||||
<% if has_archived || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
<% if has_archived || @pagy.count.nil? || @pagy.count > 0 || @template_folders.present? %>
|
||||||
<div class="mr-2">
|
<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 %>
|
<%= 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| %>
|
<% if with_subfolder %>
|
||||||
<div class="form-control my-6">
|
<%= form_for '', url: template_folder_path(@template), method: :put, data: { turbo_frame: :_top }, html: { id: 'subfolder_form', autocomplete: :off } do |f| %>
|
||||||
<folder-autocomplete class="block" data-submit-on-select="true">
|
<%= 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 %>
|
<%= f.text_field :name, required: true, placeholder: "#{t('new_folder_name')}...", class: 'base-input w-full', autofocus: true %>
|
||||||
</folder-autocomplete>
|
</folder-autocomplete>
|
||||||
</div>
|
</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.'
|
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
|
move_into_folder: Move Into Folder
|
||||||
new_folder_name: New Folder Name
|
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
|
exit_preview: Exit Preview
|
||||||
general: General
|
general: General
|
||||||
recipients: Recipients
|
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".'
|
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
|
move_into_folder: Mover a la carpeta
|
||||||
new_folder_name: Nuevo nombre de 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
|
exit_preview: Salir de la vista previa
|
||||||
general: General
|
general: General
|
||||||
recipients: Destinatarios
|
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".'
|
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
|
move_into_folder: Sposta nella cartella
|
||||||
new_folder_name: Nuovo nome della 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"
|
exit_preview: "Esci dall'anteprima"
|
||||||
general: Generale
|
general: Generale
|
||||||
recipients: Destinatari
|
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".'
|
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
|
move_into_folder: Déplacer dans le dossier
|
||||||
new_folder_name: Nouveau nom du 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"
|
exit_preview: "Quitter l'aperçu"
|
||||||
general: Général
|
general: Général
|
||||||
recipients: Destinataires
|
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".'
|
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
|
move_into_folder: Mover para pasta
|
||||||
new_folder_name: Novo nome da 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
|
exit_preview: Sair da pré-visualização
|
||||||
general: Geral
|
general: Geral
|
||||||
recipients: Destinatários
|
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".'
|
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
|
move_into_folder: In Ordner verschieben
|
||||||
new_folder_name: Neuer Ordnername
|
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
|
exit_preview: Vorschau beenden
|
||||||
general: Allgemein
|
general: Allgemein
|
||||||
recipients: Empfänger
|
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "btree_gin"
|
enable_extension "btree_gin"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -356,8 +356,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_130628) do
|
|||||||
t.datetime "archived_at"
|
t.datetime "archived_at"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_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 ["account_id"], name: "index_template_folders_on_account_id"
|
||||||
t.index ["author_id"], name: "index_template_folders_on_author_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
|
end
|
||||||
|
|
||||||
create_table "template_sharings", force: :cascade do |t|
|
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 "submitters", "submissions"
|
||||||
add_foreign_key "template_accesses", "templates"
|
add_foreign_key "template_accesses", "templates"
|
||||||
add_foreign_key "template_folders", "accounts"
|
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_folders", "users", column: "author_id"
|
||||||
add_foreign_key "template_sharings", "templates"
|
add_foreign_key "template_sharings", "templates"
|
||||||
add_foreign_key "templates", "accounts"
|
add_foreign_key "templates", "accounts"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module Submissions
|
|||||||
ALLOWED_PARAMS = %w[
|
ALLOWED_PARAMS = %w[
|
||||||
author
|
author
|
||||||
status
|
status
|
||||||
|
folder
|
||||||
completed_at_from
|
completed_at_from
|
||||||
completed_at_to
|
completed_at_to
|
||||||
created_at_from
|
created_at_from
|
||||||
@@ -24,6 +25,7 @@ module Submissions
|
|||||||
filters = normalize_filter_params(params, current_user)
|
filters = normalize_filter_params(params, current_user)
|
||||||
|
|
||||||
submissions = filter_by_author(submissions, filters, 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_status(submissions, filters)
|
||||||
submissions = filter_by_created_at(submissions, filters)
|
submissions = filter_by_created_at(submissions, filters)
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ module Submissions
|
|||||||
return submissions if filters[:author].blank?
|
return submissions if filters[:author].blank?
|
||||||
|
|
||||||
user = current_user.account.users.find_by(email: filters[:author])
|
user = current_user.account.users.find_by(email: filters[:author])
|
||||||
|
|
||||||
submissions.where(created_by_user_id: user&.id || -1)
|
submissions.where(created_by_user_id: user&.id || -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -87,6 +90,17 @@ module Submissions
|
|||||||
submissions
|
submissions
|
||||||
end
|
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)
|
def filter_by_completed_at(submissions, filters)
|
||||||
return submissions unless filters[:completed_at_from].present? || filters[:completed_at_to].present?
|
return submissions unless filters[:completed_at_from].present? || filters[:completed_at_to].present?
|
||||||
|
|
||||||
|
|||||||
+73
-1
@@ -3,15 +3,87 @@
|
|||||||
module TemplateFolders
|
module TemplateFolders
|
||||||
module_function
|
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)
|
def search(folders, keyword)
|
||||||
return folders if keyword.blank?
|
return folders if keyword.blank?
|
||||||
|
|
||||||
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||||
end
|
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)
|
def find_or_create_by_name(author, name)
|
||||||
return author.account.default_template_folder if name.blank? || name == TemplateFolder::DEFAULT_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
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user