mirror of https://github.com/docusealco/docuseal
parent
cbc6f4497a
commit
bc3b1b477e
@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionsExportController < ApplicationController
|
||||
before_action :load_template
|
||||
|
||||
def index
|
||||
submissions = @template.submissions.active
|
||||
.preload(submitters: { documents_attachments: :blob,
|
||||
attachments_attachments: :blob })
|
||||
.order(id: :asc)
|
||||
|
||||
if params[:format] == 'csv'
|
||||
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
|
||||
filename: "#{@template.name}.csv"
|
||||
elsif params[:format] == 'xlsx'
|
||||
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
|
||||
filename: "#{@template.name}.xlsx"
|
||||
end
|
||||
end
|
||||
|
||||
def new; end
|
||||
|
||||
private
|
||||
|
||||
def load_template
|
||||
@template = current_account.templates.find(params[:template_id])
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,32 @@
|
||||
<%= render 'shared/turbo_modal', title: 'Export', close_after_submit: false do %>
|
||||
<div class="space-y-2">
|
||||
<%= button_to template_submissions_export_index_path(@template), params: { format: :xlsx }, method: :get, data: { turbo_frame: :_top } do %>
|
||||
<div class="flex items-center p-4 text-left rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
||||
<div class="enabled">
|
||||
<%= svg_icon('download', class: 'w-12 h-12 stroke-2 mr-2') %>
|
||||
</div>
|
||||
<div class="disabled">
|
||||
<%= svg_icon('loader', class: 'w-12 h-12 stroke-2 mr-2 animate-spin') %>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="mb-1 text-lg font-semibold">XLSX</span>
|
||||
<p class="text-sm"> Primarily opened with Microsoft Excel. Other options include Google Sheets, LibreOffice Calc, and OpenOffice Calc.</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= button_to template_submissions_export_index_path(@template), params: { format: :csv }, method: :get, data: { turbo_frame: :_top } do %>
|
||||
<div class="flex items-center text-left p-4 rounded-2xl border border-neutral-300 hover:cursor-pointer hover:bg-neutral hover:text-gray-300">
|
||||
<div class="enabled">
|
||||
<%= svg_icon('download', class: 'w-12 h-12 stroke-2 mr-2') %>
|
||||
</div>
|
||||
<div class="disabled">
|
||||
<%= svg_icon('loader', class: 'w-12 h-12 stroke-2 mr-2 animate-spin') %>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="mb-1 text-lg font-semibold">CSV</span>
|
||||
<p class="text-sm">Can be opened with Microsoft Excel, Google Sheets, or any text editor like Notepad.</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
@ -0,0 +1,137 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submissions
|
||||
module GenerateExportFiles
|
||||
UnknownFormat = Class.new(StandardError)
|
||||
|
||||
module_function
|
||||
|
||||
def call(submissions, format: :csv)
|
||||
rows = build_table_rows(submissions)
|
||||
|
||||
if format.to_sym == :csv
|
||||
rows_to_csv(rows)
|
||||
elsif format.to_sym == :xlsx
|
||||
rows_to_xlsx(rows)
|
||||
else
|
||||
raise UnknownFormat
|
||||
end
|
||||
end
|
||||
|
||||
def rows_to_xlsx(rows)
|
||||
workbook = RubyXL::Workbook.new
|
||||
worksheet = workbook[0]
|
||||
worksheet.sheet_name = I18n.l(Time.current.to_date)
|
||||
|
||||
headers = build_headers(rows)
|
||||
headers.each_with_index do |column_name, column_index|
|
||||
worksheet.add_cell(0, column_index, column_name)
|
||||
end
|
||||
|
||||
rows.each.with_index(1) do |row, row_index|
|
||||
extract_columns(row, headers).each_with_index do |value, column_index|
|
||||
worksheet.add_cell(row_index, column_index, value)
|
||||
end
|
||||
end
|
||||
|
||||
workbook.stream.string
|
||||
end
|
||||
|
||||
def rows_to_csv(rows)
|
||||
headers = build_headers(rows)
|
||||
|
||||
CSV.generate do |csv|
|
||||
csv << headers
|
||||
|
||||
rows.each do |row|
|
||||
csv << extract_columns(row, headers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_headers(rows)
|
||||
rows.reduce(Set.new) { |acc, row| acc + row.pluck(:name) }
|
||||
end
|
||||
|
||||
def extract_columns(row, headers)
|
||||
headers.map { |key| row.find { |e| e[:name] == key }&.dig(:value) }
|
||||
end
|
||||
|
||||
def build_table_rows(submissions)
|
||||
submissions.map do |submission|
|
||||
submission_data = []
|
||||
submitters_count = submission.submitters.size
|
||||
|
||||
submission.submitters.each do |submitter|
|
||||
template_submitters = submission.template_submitters || submission.template.submitters
|
||||
submitter_name = template_submitters.find { |s| s['uuid'] == submitter.uuid }['name']
|
||||
|
||||
submission_data += build_submission_data(submitter, submitter_name, submitters_count)
|
||||
|
||||
submission_data += submitter_formatted_fields(submitter).map do |field|
|
||||
{
|
||||
name: column_name(field[:name], submitter_name, submitters_count),
|
||||
value: field[:value]
|
||||
}
|
||||
end
|
||||
|
||||
next if submitter != submission.submitters.select(&:completed_at?).max_by(&:completed_at)
|
||||
|
||||
submission_data += submitter.documents.map.with_index(1) do |attachment, index|
|
||||
{
|
||||
name: "Document #{index}",
|
||||
value: attachment.url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
submission_data
|
||||
end
|
||||
end
|
||||
|
||||
def build_submission_data(submitter, submitter_name, submitters_count)
|
||||
[
|
||||
{
|
||||
name: column_name('Email', submitter_name, submitters_count),
|
||||
value: submitter.email
|
||||
},
|
||||
{
|
||||
name: column_name('Completed At', submitter_name, submitters_count),
|
||||
value: submitter.completed_at
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def column_name(name, submitter_name, submitters_count = 1)
|
||||
submitters_count > 1 ? "#{submitter_name} - #{name}" : name
|
||||
end
|
||||
|
||||
def submitter_formatted_fields(submitter)
|
||||
fields = submitter.submission.template_fields || submitter.submission.template.fields
|
||||
|
||||
template_fields = fields.select { |f| f['submitter_uuid'] == submitter.uuid }
|
||||
|
||||
attachments_index = submitter.attachments.index_by(&:uuid)
|
||||
|
||||
template_field_counters = Hash.new { 0 }
|
||||
template_fields.map do |template_field|
|
||||
submitter_value = submitter.values.fetch(template_field['uuid'], nil)
|
||||
template_field_type = template_field['type']
|
||||
template_field_counters[template_field_type] += 1
|
||||
template_field_name = template_field['name'].presence
|
||||
template_field_name ||= "#{template_field_type.titleize} Field #{template_field_counters[template_field_type]}"
|
||||
|
||||
value =
|
||||
if template_field_type.in?(%w[image signature])
|
||||
attachments_index[submitter_value]&.url
|
||||
elsif template_field_type == 'file'
|
||||
Array.wrap(submitter_value).compact_blank.filter_map { |e| attachments_index[e]&.url }
|
||||
else
|
||||
submitter_value
|
||||
end
|
||||
|
||||
{ name: template_field_name, uuid: template_field['uuid'], value: }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in new issue