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