mirror of
https://github.com/docusealco/docuseal.git
synced 2026-06-23 04:10:11 +00:00
adjust form completed step
This commit is contained in:
@@ -27,7 +27,6 @@ gem 'sqlite3'
|
||||
gem 'strip_attributes'
|
||||
gem 'turbo-rails'
|
||||
gem 'tzinfo-data'
|
||||
gem 'zip'
|
||||
|
||||
group :development, :test do
|
||||
gem 'annotate'
|
||||
|
||||
@@ -6,8 +6,8 @@ class SubmissionsDownloadController < ApplicationController
|
||||
def index
|
||||
submitter = Submitter.find_by(slug: params[:submitter_slug])
|
||||
|
||||
Submissions::GenerateResultAttachments.call(submitter)
|
||||
Submissions::GenerateResultAttachments.call(submitter) if submitter.documents.blank?
|
||||
|
||||
redirect_to submitter.archive.url, allow_other_host: true
|
||||
render json: submitter.documents.map { |e| helpers.rails_blob_url(e) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ class SubmitFormController < ApplicationController
|
||||
Submitter.preload(submission: { template: { documents_attachments: { preview_images_attachments: :blob } } })
|
||||
.find_by!(slug: params[:slug])
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -30,6 +30,12 @@ class SubmitFormController < ApplicationController
|
||||
private
|
||||
|
||||
def normalized_values
|
||||
params[:values].to_unsafe_h.transform_values { |v| v.is_a?(Array) ? v.compact_blank : v }
|
||||
params[:values].to_unsafe_h.transform_values do |v|
|
||||
if params[:cast_boolean] == 'true'
|
||||
v == 'true'
|
||||
else
|
||||
v.is_a?(Array) ? v.compact_blank : v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,6 +33,6 @@ class TemplatesController < ApplicationController
|
||||
private
|
||||
|
||||
def template_params
|
||||
params.require(:template).permit(:name, :schema)
|
||||
params.require(:template).permit(:name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,6 +47,10 @@ select:required:invalid {
|
||||
@apply btn btn-neutral text-white text-base;
|
||||
}
|
||||
|
||||
.white-button {
|
||||
@apply btn btn-outline text-base bg-white border-2;
|
||||
}
|
||||
|
||||
.base-checkbox {
|
||||
@apply checkbox rounded bg-white checkbox-sm;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex cursor-pointer bg-red-100 absolute border text-[1.5vw] lg:text-base"
|
||||
class="flex absolute text-[1.5vw] lg:text-base"
|
||||
:style="computedStyle"
|
||||
:class="{ 'border-red-100': !isActive, 'bg-opacity-70': !isActive && !isValue, 'border-red-500 border-dashed z-10': isActive, 'bg-opacity-30': isActive || isValue }"
|
||||
:class="{ 'cursor-default': !submittable, 'bg-red-100 border cursor-pointer ': submittable, 'border-red-100': !isActive && submittable, 'bg-opacity-70': !isActive && !isValueSet && submittable, 'border-red-500 border-dashed z-10': isActive && submittable, 'bg-opacity-30': (isActive || isValueSet) && submittable }"
|
||||
>
|
||||
<div
|
||||
v-if="!isActive && !isValue && field.type !== 'checkbox'"
|
||||
v-if="!isActive && !isValueSet && field.type !== 'checkbox' && submittable"
|
||||
class="absolute top-0 bottom-0 right-0 left-0 items-center justify-center h-full w-full"
|
||||
>
|
||||
<span
|
||||
@@ -65,6 +65,7 @@
|
||||
class="w-full p-[0.2vw] flex items-center justify-center"
|
||||
>
|
||||
<input
|
||||
v-if="submittable"
|
||||
type="checkbox"
|
||||
:value="false"
|
||||
class="aspect-square base-checkbox"
|
||||
@@ -72,6 +73,12 @@
|
||||
:checked="!!modelValue"
|
||||
@click="$emit('update:model-value', !modelValue)"
|
||||
>
|
||||
<component
|
||||
:is="modelValue ? 'IconCheckbox' : 'IconSquare'"
|
||||
v-else
|
||||
class="aspect-square"
|
||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -91,27 +98,29 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks } from '@tabler/icons-vue'
|
||||
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconSquare } from '@tabler/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'FieldArea',
|
||||
components: {
|
||||
IconPaperclip
|
||||
IconPaperclip,
|
||||
IconCheckbox,
|
||||
IconSquare
|
||||
},
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
step: {
|
||||
type: Array,
|
||||
isValueSet: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: () => []
|
||||
default: false
|
||||
},
|
||||
values: {
|
||||
type: Object,
|
||||
submittable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
default: false
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String, Number, Object, Boolean],
|
||||
@@ -153,9 +162,6 @@ export default {
|
||||
multiple: 'Multiple Select'
|
||||
}
|
||||
},
|
||||
isValue () {
|
||||
return this.step.some((f) => ![null, undefined, ''].includes(this.values[f.uuid]))
|
||||
},
|
||||
fieldIcons () {
|
||||
return {
|
||||
text: IconTextSize,
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
:ref="setAreaRef"
|
||||
v-model="values[field.uuid]"
|
||||
:field="field"
|
||||
:values="values"
|
||||
:area="area"
|
||||
:submittable="true"
|
||||
:field-index="fieldIndex"
|
||||
:step="step"
|
||||
:is-active="currentStep === step"
|
||||
:is-value-set="step.some((f) => f.uuid in values)"
|
||||
:attachments-index="attachmentsIndex"
|
||||
@click="$emit('focus-step', step)"
|
||||
/>
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>
|
||||
Form completed - thanks!
|
||||
<div class="mx-auto max-w-md flex flex-col">
|
||||
<p class="font-medium text-2xl flex items-center space-x-1.5 mx-auto">
|
||||
<IconCircleCheck
|
||||
class="inline text-green-600"
|
||||
:width="30"
|
||||
:height="30"
|
||||
/>
|
||||
<span>
|
||||
Form has been completed!
|
||||
</span>
|
||||
</p>
|
||||
<button @click.prevent="sendCopyToEmail">
|
||||
<span v-if="isSendingCopy">
|
||||
Sending
|
||||
</span>
|
||||
<span>
|
||||
Send copy to email
|
||||
</span>
|
||||
</button>
|
||||
<button @click.prevent="download">
|
||||
<span v-if="isDownloading">
|
||||
Downloading
|
||||
</span>
|
||||
<span>
|
||||
Download copy
|
||||
</span>
|
||||
</button>
|
||||
<div class="space-y-3 mt-5">
|
||||
<button
|
||||
class="white-button flex items-center space-x-1 w-full"
|
||||
:disabled="isSendingCopy"
|
||||
@click.prevent="sendCopyToEmail"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
v-if="isSendingCopy"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<IconMail v-else />
|
||||
<span>
|
||||
Send copy via email
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="base-button flex items-center space-x-1 w-full"
|
||||
:disabled="isDownloading"
|
||||
@click.prevent="download"
|
||||
>
|
||||
<IconInnerShadowTop
|
||||
v-if="isDownloading"
|
||||
class="animate-spin"
|
||||
/>
|
||||
<IconDownload v-else />
|
||||
<span>
|
||||
Download
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IconCircleCheck, IconMail, IconDownload, IconInnerShadowTop } from '@tabler/icons-vue'
|
||||
import confetti from 'canvas-confetti'
|
||||
|
||||
export default {
|
||||
name: 'FormCompleted',
|
||||
components: {
|
||||
IconCircleCheck,
|
||||
IconInnerShadowTop,
|
||||
IconMail,
|
||||
IconDownload
|
||||
},
|
||||
props: {
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
@@ -37,12 +67,21 @@ export default {
|
||||
isDownloading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
confetti({
|
||||
particleCount: 50,
|
||||
startVelocity: 30,
|
||||
spread: 140
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
sendCopyToEmail () {
|
||||
this.isSendingCopy = true
|
||||
|
||||
fetch(`/send_submission_email.json?submitter_slug=${this.submitterSlug}`, {
|
||||
method: 'POST'
|
||||
}).then(() => {
|
||||
alert('Email has been sent')
|
||||
}).finally(() => {
|
||||
this.isSendingCopy = false
|
||||
})
|
||||
@@ -50,17 +89,21 @@ export default {
|
||||
download () {
|
||||
this.isDownloading = true
|
||||
|
||||
fetch(`/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||
const blob = new Blob([await response.text()], { type: `${response.headers.get('content-type')};charset=utf-8;` })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
fetch(`/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => {
|
||||
urls.forEach((url) => {
|
||||
fetch(url).then(async (response) => {
|
||||
const blob = new Blob([await response.text()], { type: `${response.headers.get('content-type')};charset=utf-8;` })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
|
||||
link.href = url
|
||||
link.setAttribute('download', response.headers.get('content-disposition').split('"')[1])
|
||||
link.href = url
|
||||
link.setAttribute('download', response.headers.get('content-disposition').split('"')[1])
|
||||
|
||||
link.click()
|
||||
link.click()
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
URL.revokeObjectURL(url)
|
||||
})
|
||||
})
|
||||
}).finally(() => {
|
||||
this.isDownloading = false
|
||||
})
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
<template>
|
||||
<template
|
||||
v-for="field in otherSubmitterFields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<Teleport
|
||||
v-for="(area, index) in field.areas"
|
||||
:key="index"
|
||||
:to="`#page-${area.attachment_uuid}-${area.page}`"
|
||||
>
|
||||
<FieldArea
|
||||
:model-value="values[field.uuid]"
|
||||
:field="field"
|
||||
:area="area"
|
||||
:attachments-index="attachmentsIndex"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<FieldAreas
|
||||
ref="areas"
|
||||
:steps="stepFields"
|
||||
@@ -7,206 +24,221 @@
|
||||
:current-step="currentStepFields"
|
||||
@focus-step="goToStep($event, false, true)"
|
||||
/>
|
||||
<form
|
||||
v-if="!isCompleted"
|
||||
ref="form"
|
||||
:action="submitPath"
|
||||
method="post"
|
||||
class="md:mx-16"
|
||||
@submit.prevent="submitStep"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="authenticity_token"
|
||||
:value="authenticityToken"
|
||||
<div>
|
||||
<form
|
||||
v-if="!isCompleted"
|
||||
ref="form"
|
||||
:action="submitPath"
|
||||
method="post"
|
||||
class="md:mx-16"
|
||||
@submit.prevent="submitStep"
|
||||
>
|
||||
<input
|
||||
v-if="currentStep === stepFields.length - 1"
|
||||
type="hidden"
|
||||
name="completed"
|
||||
value="true"
|
||||
>
|
||||
<input
|
||||
value="put"
|
||||
name="_method"
|
||||
type="hidden"
|
||||
>
|
||||
<div class="mt-4">
|
||||
<div v-if="currentField.type === 'text'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div>
|
||||
<input
|
||||
<input
|
||||
type="hidden"
|
||||
name="authenticity_token"
|
||||
:value="authenticityToken"
|
||||
>
|
||||
<input
|
||||
v-if="currentStep === stepFields.length - 1"
|
||||
type="hidden"
|
||||
name="completed"
|
||||
value="true"
|
||||
>
|
||||
<input
|
||||
value="put"
|
||||
name="_method"
|
||||
type="hidden"
|
||||
>
|
||||
<div class="mt-4">
|
||||
<div v-if="currentField.type === 'text'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div>
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
class="base-input !text-2xl w-full"
|
||||
:required="currentField.required"
|
||||
placeholder="Type here..."
|
||||
type="text"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'date'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div class="text-center">
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
class="base-input !text-2xl text-center w-full"
|
||||
:required="currentField.required"
|
||||
type="date"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'select'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<select
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
class="base-input !text-2xl w-full"
|
||||
:required="currentField.required"
|
||||
placeholder="Type here..."
|
||||
type="text"
|
||||
:required="true"
|
||||
class="select base-input !text-2xl w-full text-center font-normal"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@change="values[currentField.uuid] = $event.target.value"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'date'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div class="text-center">
|
||||
<input
|
||||
:id="currentField.uuid"
|
||||
v-model="values[currentField.uuid]"
|
||||
class="base-input !text-2xl text-center w-full"
|
||||
:required="currentField.required"
|
||||
type="date"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'select'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<select
|
||||
:id="currentField.uuid"
|
||||
:required="true"
|
||||
class="select base-input !text-2xl w-full text-center font-normal"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
@change="values[currentField.uuid] = $event.target.value"
|
||||
@focus="$refs.areas.scrollIntoField(currentField)"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
:selected="!values[currentField.uuid]"
|
||||
>
|
||||
Select your option
|
||||
</option>
|
||||
<option
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
:selected="values[currentField.uuid] == option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'radio'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div class="flex w-full">
|
||||
<div class="space-y-3.5 mx-auto">
|
||||
<div
|
||||
<option
|
||||
value=""
|
||||
:selected="!values[currentField.uuid]"
|
||||
>
|
||||
Select your option
|
||||
</option>
|
||||
<option
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
:selected="values[currentField.uuid] == option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-else-if="currentField.type === 'radio'">
|
||||
<label
|
||||
v-if="currentField.name"
|
||||
:for="currentField.uuid"
|
||||
class="label text-2xl mb-2"
|
||||
>{{ currentField.name }}</label>
|
||||
<div class="flex w-full">
|
||||
<div class="space-y-3.5 mx-auto">
|
||||
<div
|
||||
v-for="(option, index) in currentField.options"
|
||||
:key="index"
|
||||
>
|
||||
<label
|
||||
:for="currentField.uuid + option"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<input
|
||||
:id="currentField.uuid + option"
|
||||
v-model="values[currentField.uuid]"
|
||||
type="radio"
|
||||
class="base-radio !h-7 !w-7"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
:value="option"
|
||||
required
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ option }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MultiSelectStep
|
||||
v-else-if="currentField.type === 'multiple'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
/>
|
||||
<div
|
||||
v-else-if="currentField.type === 'checkbox'"
|
||||
class="flex w-full"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="cast_boolean"
|
||||
value="true"
|
||||
>
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
>
|
||||
<div
|
||||
v-for="(field, index) in currentStepFields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<label
|
||||
:for="currentField.uuid + option"
|
||||
:for="field.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<input
|
||||
:id="currentField.uuid + option"
|
||||
v-model="values[currentField.uuid]"
|
||||
type="radio"
|
||||
class="base-radio !h-7 !w-7"
|
||||
:name="`values[${currentField.uuid}]`"
|
||||
:value="option"
|
||||
required
|
||||
type="hidden"
|
||||
:name="`values[${field.uuid}]`"
|
||||
:value="!!values[field.uuid]"
|
||||
>
|
||||
<input
|
||||
:id="field.uuid"
|
||||
type="checkbox"
|
||||
class="base-checkbox !h-7 !w-7"
|
||||
:checked="!!values[field.uuid]"
|
||||
@click="values[field.uuid] = !values[field.uuid]"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ option }}
|
||||
{{ currentField.name || currentField.type + ' ' + (index + 1) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ImageStep
|
||||
v-else-if="currentField.type === 'image'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
<SignatureStep
|
||||
v-else-if="currentField.type === 'signature'"
|
||||
ref="currentStep"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
<AttachmentStep
|
||||
v-else-if="currentField.type === 'file'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
</div>
|
||||
<MultiSelectStep
|
||||
v-else-if="currentField.type === 'multiple'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
/>
|
||||
<div
|
||||
v-else-if="currentField.type === 'checkbox'"
|
||||
class="flex w-full"
|
||||
>
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
<div class="mt-8">
|
||||
<button
|
||||
type="submit"
|
||||
class="base-button w-full"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<div
|
||||
v-for="(field, index) in currentStepFields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<label
|
||||
:for="field.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<input
|
||||
:id="field.uuid"
|
||||
type="checkbox"
|
||||
:name="`values[${field.uuid}]`"
|
||||
:value="false"
|
||||
class="base-checkbox !h-7 !w-7"
|
||||
:checked="!!values[field.uuid]"
|
||||
@click="values[field.uuid] = !values[field.uuid]"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ currentField.name || currentField.type + ' ' + (index + 1) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="isSubmitting">
|
||||
Submitting...
|
||||
</span>
|
||||
<span v-else>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<ImageStep
|
||||
v-else-if="currentField.type === 'image'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
<SignatureStep
|
||||
v-else-if="currentField.type === 'signature'"
|
||||
ref="currentStep"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
<AttachmentStep
|
||||
v-else-if="currentField.type === 'file'"
|
||||
v-model="values[currentField.uuid]"
|
||||
:field="currentField"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:submitter-slug="submitterSlug"
|
||||
@attached="attachments.push($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<button
|
||||
type="submit"
|
||||
class="base-button w-full"
|
||||
>
|
||||
<span v-if="isSubmitting">
|
||||
Submitting...
|
||||
</span>
|
||||
<span v-else>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<FormCompleted
|
||||
v-else
|
||||
:submitter-slug="submitterSlug"
|
||||
/>
|
||||
<div class="flex justify-center">
|
||||
<div class="flex items-center mt-5 mb-1">
|
||||
<a
|
||||
@@ -214,20 +246,17 @@
|
||||
:key="step[0].uuid"
|
||||
href="#"
|
||||
class="inline border border-base-300 h-3 w-3 rounded-full mx-1"
|
||||
:class="{ 'bg-base-200': index === currentStep, 'bg-base-content': index < currentStep, 'bg-white': index > currentStep }"
|
||||
@click.prevent="goToStep(step, true)"
|
||||
:class="{ 'bg-base-200': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }"
|
||||
@click.prevent="isCompleted ? '' : goToStep(step, true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<FormCompleted
|
||||
v-else
|
||||
:submitter-slug="submitterSlug"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FieldAreas from './areas'
|
||||
import FieldArea from './area'
|
||||
import ImageStep from './image_step'
|
||||
import SignatureStep from './signature_step'
|
||||
import AttachmentStep from './attachment_step'
|
||||
@@ -238,6 +267,7 @@ export default {
|
||||
name: 'SubmissionForm',
|
||||
components: {
|
||||
FieldAreas,
|
||||
FieldArea,
|
||||
ImageStep,
|
||||
SignatureStep,
|
||||
AttachmentStep,
|
||||
@@ -287,11 +317,14 @@ export default {
|
||||
currentField () {
|
||||
return this.currentStepFields[0]
|
||||
},
|
||||
submitterFields () {
|
||||
currentSubmitterFields () {
|
||||
return this.fields.filter((f) => f.submitter_uuid === this.submitterUuid)
|
||||
},
|
||||
otherSubmitterFields () {
|
||||
return this.fields.filter((f) => f.submitter_uuid !== this.submitterUuid)
|
||||
},
|
||||
stepFields () {
|
||||
return this.submitterFields.reduce((acc, f) => {
|
||||
return this.currentSubmitterFields.reduce((acc, f) => {
|
||||
const prevStep = acc[acc.length - 1]
|
||||
|
||||
if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox') {
|
||||
|
||||
@@ -36,9 +36,7 @@ class Submitter < ApplicationRecord
|
||||
|
||||
serialize :values, JSON
|
||||
|
||||
has_one_attached :archive
|
||||
has_many_attached :documents
|
||||
|
||||
has_many_attached :attachments
|
||||
|
||||
def status
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<% attachment_field_uuids = @submitter.submission.template.fields.select { |f| f['type'].in?(%w[image signature file]) }.pluck('uuid') %>
|
||||
<% attachments = ActiveStorage::Attachment.where(uuid: @submitter.values.values_at(*attachment_field_uuids).flatten).preload(:blob) %>
|
||||
<div class="mx-auto block pb-72" style="max-width: 1000px">
|
||||
<% values = @submitter.submission.submitters.reduce({}) { |acc, e| acc.merge(e.values) } %>
|
||||
<% attachments = ActiveStorage::Attachment.where(uuid: values.values_at(*attachment_field_uuids).flatten).preload(:blob) %>
|
||||
<div class="mx-auto block pb-72 select-none" style="max-width: 1000px">
|
||||
<div class="mt-4 flex">
|
||||
<a href="<%= root_path %>" class="mx-auto text-2xl md:text-3xl font-bold items-center flex space-x-3">
|
||||
<%= render 'shared/logo', class: 'w-9 h-9 md:w-12 md:h-12' %>
|
||||
@@ -25,7 +26,7 @@
|
||||
<div class="mx-auto" style="max-width: 1000px">
|
||||
<div class="relative md:mx-32">
|
||||
<div class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border border-base-200 p-4 rounded">
|
||||
<submission-form data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-attachments="<%= attachments.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
||||
<submission-form data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-attachments="<%= attachments.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.to_json %>" data-values="<%= values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,11 @@ module Submissions
|
||||
cert = submitter.submission.template.account.encrypted_configs
|
||||
.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
|
||||
|
||||
zip_file = Tempfile.new
|
||||
zip_stream = Zip::ZipOutputStream.open(zip_file)
|
||||
|
||||
pdfs_index = build_pdfs_index(submitter)
|
||||
|
||||
template.fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
|
||||
field.fetch('areas', []).each do |area|
|
||||
pdf = pdfs_index[area['attachment_uuid']]
|
||||
|
||||
@@ -110,9 +109,6 @@ module Submissions
|
||||
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
|
||||
OpenSSL::X509::Certificate.new(cert['root_ca'])])
|
||||
|
||||
zip_stream.put_next_entry("#{item['name']}.pdf")
|
||||
zip_stream.write(io.string)
|
||||
|
||||
ActiveStorage::Attachment.create!(
|
||||
uuid: item['attachment_uuid'],
|
||||
blob: ActiveStorage::Blob.create_and_upload!(
|
||||
@@ -122,10 +118,6 @@ module Submissions
|
||||
record: submitter
|
||||
)
|
||||
end
|
||||
|
||||
zip_stream.close
|
||||
|
||||
submitter.archive.attach(io: zip_file, filename: "#{template.name}.zip")
|
||||
end
|
||||
# rubocop:enable Metrics
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"compression-webpack-plugin": "10.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
|
||||
@@ -1943,6 +1943,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz#d19d7b6e913afae3e98f023db97c19e9ddc5e91f"
|
||||
integrity sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==
|
||||
|
||||
canvas-confetti@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.6.0.tgz#193f71aa8f38fc850a5ba94f59091a7afdb43ead"
|
||||
integrity sha512-ej+w/m8Jzpv9Z7W7uJZer14Ke8P2ogsjg4ZMGIuq4iqUOqY2Jq8BNW42iGmNfRwREaaEfFIczLuZZiEVSYNHAA==
|
||||
|
||||
chalk@^2.0.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
|
||||
Reference in New Issue
Block a user