@@ -5,19 +5,16 @@
|
||||
@close="emit('close')"
|
||||
>
|
||||
<open-form
|
||||
v-if="form"
|
||||
:form-manager="formManager"
|
||||
:theme="theme"
|
||||
:loading="false"
|
||||
:form="form"
|
||||
:fields="form.properties"
|
||||
:default-data-form="submission"
|
||||
:mode="FormMode.EDIT"
|
||||
@submit="updateForm"
|
||||
>
|
||||
<template #submit-btn="{ submitForm }">
|
||||
<template #submit-btn="{ loading }">
|
||||
<v-button
|
||||
:loading="loading"
|
||||
class="mt-2 px-8 mx-1"
|
||||
@click.prevent="submitForm"
|
||||
@click.prevent="updateForm"
|
||||
>
|
||||
Update Submission
|
||||
</v-button>
|
||||
@@ -25,11 +22,13 @@
|
||||
</open-form>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits } from "vue"
|
||||
import OpenForm from "../forms/OpenForm.vue"
|
||||
import CachedDefaultTheme from "~/lib/forms/themes/CachedDefaultTheme.js"
|
||||
import { FormMode } from "~/lib/forms/FormModeStrategy.js"
|
||||
import { useFormManager } from '~/lib/forms/composables/useFormManager'
|
||||
|
||||
const props = defineProps({
|
||||
show: { type: Boolean, required: true },
|
||||
@@ -46,13 +45,37 @@ const props = defineProps({
|
||||
submission: { type: Object },
|
||||
})
|
||||
|
||||
// Set up form manager with proper mode
|
||||
let formManager = null
|
||||
const setupFormManager = () => {
|
||||
if (!props.form) return null
|
||||
|
||||
formManager = useFormManager(props.form, FormMode.EDIT)
|
||||
|
||||
return formManager
|
||||
}
|
||||
|
||||
// Initialize form manager
|
||||
formManager = setupFormManager()
|
||||
|
||||
watch(() => props.show, (newShow) => {
|
||||
if (newShow) {
|
||||
nextTick(() => {
|
||||
formManager.initialize({
|
||||
skipPendingSubmission: true,
|
||||
skipUrlParams: true,
|
||||
defaultData: props.submission
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const emit = defineEmits(["close", "updated"])
|
||||
const updateForm = (form, onFailure) => {
|
||||
const updateForm = () => {
|
||||
loading.value = true
|
||||
form
|
||||
.put("/open/forms/" + props.form.id + "/submissions/" + props.submission.id)
|
||||
formManager.form.put("/open/forms/" + props.form.id + "/submissions/" + props.submission.id)
|
||||
.then((res) => {
|
||||
useAlert().success(res.message)
|
||||
loading.value = false
|
||||
@@ -65,7 +88,6 @@ const updateForm = (form, onFailure) => {
|
||||
useAlert().formValidationError(error.data)
|
||||
}
|
||||
loading.value = false
|
||||
onFailure()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
orientation="horizontal"
|
||||
>
|
||||
<UButton
|
||||
v-track.delete_record_click
|
||||
v-track.edit_record_click
|
||||
size="sm"
|
||||
color="white"
|
||||
icon="heroicons:pencil-square"
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
class="m-2 my-4">
|
||||
<UAlert
|
||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
||||
color="yellow"
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
icon="i-material-symbols-info-outline"
|
||||
@close="hidePasswordDisabledMsg = true"
|
||||
@@ -64,29 +64,35 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
<UAlert
|
||||
v-if="isPublicFormPage && (form.is_closed || form.visibility=='closed')"
|
||||
class="border shadow-sm p-2 my-4 flex items-center rounded-md bg-yellow-100 dark:bg-yellow-600/20 border-yellow-500 dark:border-yellow-500/20"
|
||||
icon="i-heroicons-lock-closed-20-solid"
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
class="m-2 my-4"
|
||||
>
|
||||
<div class="flex-grow">
|
||||
<template #description>
|
||||
<div
|
||||
class="mb-0 py-2 px-4 text-yellow-600"
|
||||
class="py-2"
|
||||
v-html="form.closed_text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
||||
<div
|
||||
<UAlert
|
||||
v-else-if="isPublicFormPage && form.max_number_of_submissions_reached"
|
||||
class="border shadow-sm p-2 my-4 flex items-center rounded-md bg-yellow-100 dark:bg-yellow-600/20 border-yellow-500 dark:border-yellow-500/20"
|
||||
icon="i-heroicons-lock-closed-20-solid"
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
class="m-2 my-4"
|
||||
>
|
||||
<div class="flex-grow">
|
||||
<template #description>
|
||||
<div
|
||||
class="mb-0 py-2 px-4 text-yellow-600 dark:text-yellow-600"
|
||||
class="py-2"
|
||||
v-html="form.max_submissions_reached_text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UAlert>
|
||||
|
||||
<form-cleanings
|
||||
v-if="showFormCleanings"
|
||||
@@ -343,7 +349,7 @@ const triggerSubmit = async () => {
|
||||
submissionId: submissionId.value
|
||||
}).then(result => {
|
||||
if (result) {
|
||||
submittedData.value = result || {}
|
||||
submittedData.value = formManager.form.data()
|
||||
|
||||
if (result?.submission_id) {
|
||||
submissionId.value = result.submission_id
|
||||
@@ -392,7 +398,9 @@ const addPasswordError = (msg) => {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addPasswordError
|
||||
addPasswordError,
|
||||
restart,
|
||||
formManager
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<slot
|
||||
v-if="isLastPage"
|
||||
name="submit-btn"
|
||||
:loading="form.busy"
|
||||
:loading="isProcessing"
|
||||
/>
|
||||
<open-form-button
|
||||
v-else-if="currentFieldsPageBreak"
|
||||
@@ -73,7 +73,7 @@
|
||||
:color="form.color"
|
||||
:theme="theme"
|
||||
class="mt-2 px-8 mx-1"
|
||||
:loading="form.busy"
|
||||
:loading="isProcessing"
|
||||
@click.stop="handleNextClick"
|
||||
>
|
||||
{{ currentFieldsPageBreak.next_btn_text }}
|
||||
@@ -165,6 +165,8 @@ const handleDragDropped = (data) => {
|
||||
workingFormStore.moveField(oldTargetIndex, newTargetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const isProcessing = computed(() => props.formManager.state.isProcessing)
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
]"
|
||||
/>
|
||||
|
||||
<div class="flex-grow flex justify-center">
|
||||
<div class="flex-grow flex justify-center gap-2">
|
||||
<EditableTag
|
||||
id="form-editor-title"
|
||||
v-model="form.title"
|
||||
@@ -34,12 +34,14 @@
|
||||
v-if="form.visibility == 'draft'"
|
||||
color="yellow"
|
||||
variant="soft"
|
||||
icon="i-heroicons-pencil-square"
|
||||
label="Draft"
|
||||
/>
|
||||
<UBadge
|
||||
v-else-if="form.visibility == 'closed'"
|
||||
color="gray"
|
||||
variant="soft"
|
||||
icon="i-heroicons-lock-closed-20-solid"
|
||||
label="Closed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
110
client/components/open/forms/components/FormStatusBadges.vue
Normal file
110
client/components/open/forms/components/FormStatusBadges.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="shouldDisplayBadges"
|
||||
class="flex items-center flex-wrap gap-1"
|
||||
>
|
||||
<!-- Draft Badge -->
|
||||
<UTooltip v-if="form.visibility === 'draft'" text="Not publicly accessible">
|
||||
<UBadge
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
:size="size"
|
||||
>
|
||||
Draft
|
||||
</UBadge>
|
||||
</UTooltip>
|
||||
|
||||
<!-- Closed Badge -->
|
||||
<UTooltip v-else-if="form.visibility === 'closed'" text="Won't accept new submissions">
|
||||
<UBadge
|
||||
color="gray"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-lock-closed"
|
||||
:size="size"
|
||||
>
|
||||
Closed
|
||||
</UBadge>
|
||||
</UTooltip>
|
||||
|
||||
<!-- Time Limited Badge -->
|
||||
<UTooltip v-if="form.closes_at && !form.is_closed" :text="`Will close on ${closesDate}`">
|
||||
<UBadge
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-clock"
|
||||
:size="size"
|
||||
>
|
||||
Time limited
|
||||
</UBadge>
|
||||
</UTooltip>
|
||||
|
||||
<!-- Submission Limited Badge -->
|
||||
<UTooltip
|
||||
v-if="form.max_submissions_count > 0 && !form.max_number_of_submissions_reached"
|
||||
:text="`Limited to ${form.max_submissions_count} submissions`"
|
||||
>
|
||||
<UBadge
|
||||
color="amber"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-chart-bar"
|
||||
:size="size"
|
||||
>
|
||||
Submission limited
|
||||
</UBadge>
|
||||
</UTooltip>
|
||||
|
||||
<!-- Tags Badges -->
|
||||
<UBadge
|
||||
v-for="tag in form.tags"
|
||||
:key="tag"
|
||||
color="white"
|
||||
variant="solid"
|
||||
class="capitalize"
|
||||
:size="size"
|
||||
>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
form: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'sm',
|
||||
validator: (value) => ['xs', 'sm', 'md', 'lg'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
const closesDate = computed(() => {
|
||||
if (props.form && props.form.closes_at) {
|
||||
try {
|
||||
const dateObj = new Date(props.form.closes_at)
|
||||
return dateObj.getFullYear() + '-' +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(dateObj.getDate()).padStart(2, '0') + ' ' +
|
||||
String(dateObj.getHours()).padStart(2, '0') + ':' +
|
||||
String(dateObj.getMinutes()).padStart(2, '0')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
// Conditional to determine if badges should be displayed
|
||||
const shouldDisplayBadges = computed(() => {
|
||||
return ['draft', 'closed'].includes(props.form.visibility) ||
|
||||
(props.form.tags && props.form.tags.length > 0) ||
|
||||
props.form.closes_at ||
|
||||
(props.form.max_submissions_count > 0)
|
||||
})
|
||||
</script>
|
||||
@@ -142,6 +142,13 @@ watch(() => form.value.dark_mode, () => {
|
||||
handleDarkModeChange()
|
||||
})
|
||||
|
||||
// Watch for form mode changes to reset the form when switching modes
|
||||
watch(formMode, () => {
|
||||
if (previewFormSubmitted.value) {
|
||||
restartForm()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
handleDarkModeChange()
|
||||
})
|
||||
@@ -163,7 +170,16 @@ function handleDarkModeChange() {
|
||||
|
||||
function restartForm() {
|
||||
previewFormSubmitted.value = false
|
||||
formPreview.value.restart()
|
||||
|
||||
try {
|
||||
// Try using the component reference first
|
||||
if (formPreview.value && typeof formPreview.value.restart === 'function') {
|
||||
formPreview.value.restart()
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error restarting form:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExpand() {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
:options="visibilityOptions"
|
||||
/>
|
||||
<div
|
||||
v-if="form.closes_at || form.visibility == 'closed'"
|
||||
v-if="isFormClosingOrClosed"
|
||||
class="bg-gray-50 border rounded-lg px-4 py-2"
|
||||
>
|
||||
<rich-text-area-input
|
||||
@@ -112,119 +112,103 @@
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import clonedeep from "clone-deep"
|
||||
import { default as _has } from "lodash/has"
|
||||
<script setup>
|
||||
import clonedeep from 'clone-deep'
|
||||
import { default as _has } from 'lodash/has'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const formsStore = useFormsStore()
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
const { getAll: forms } = storeToRefs(formsStore)
|
||||
// Store setup
|
||||
const formsStore = useFormsStore()
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
const { content: form } = storeToRefs(workingFormStore)
|
||||
const forms = computed(() => formsStore.getAll)
|
||||
|
||||
// Reactive state
|
||||
const showCopyFormSettingsModal = ref(false)
|
||||
const copyFormId = ref(null)
|
||||
|
||||
// Computed properties
|
||||
const visibilityOptions = [
|
||||
{
|
||||
name: 'Published',
|
||||
value: 'public',
|
||||
},
|
||||
{
|
||||
name: 'Draft - not publicly accessible',
|
||||
value: 'draft',
|
||||
},
|
||||
{
|
||||
name: 'Closed - won\'t accept new submissions',
|
||||
value: 'closed',
|
||||
},
|
||||
]
|
||||
|
||||
const copyFormOptions = computed(() => {
|
||||
return forms.value
|
||||
.filter((formItem) => {
|
||||
return form.value.id !== formItem.id
|
||||
})
|
||||
.map((formItem) => {
|
||||
return {
|
||||
name: formItem.title,
|
||||
value: formItem.id,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const allTagsOptions = computed(() => {
|
||||
return formsStore.allTags.map((tagname) => {
|
||||
return {
|
||||
forms,
|
||||
formsStore,
|
||||
workingFormStore,
|
||||
name: tagname,
|
||||
value: tagname,
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
data() {
|
||||
return {
|
||||
showCopyFormSettingsModal: false,
|
||||
copyFormId: null,
|
||||
visibilityOptions: [
|
||||
{
|
||||
name: "Published",
|
||||
value: "public",
|
||||
},
|
||||
{
|
||||
name: "Draft - not publicly accessible",
|
||||
value: "draft",
|
||||
},
|
||||
{
|
||||
name: "Closed - won't accept new submissions",
|
||||
value: "closed",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
// New computed property for v-if condition
|
||||
const isFormClosingOrClosed = computed(() => {
|
||||
return form.value.closes_at || form.value.visibility === 'closed'
|
||||
})
|
||||
|
||||
computed: {
|
||||
copyFormOptions() {
|
||||
return this.forms
|
||||
.filter((form) => {
|
||||
return this.form.id !== form.id
|
||||
})
|
||||
.map((form) => {
|
||||
return {
|
||||
name: form.title,
|
||||
value: form.id,
|
||||
}
|
||||
})
|
||||
},
|
||||
form: {
|
||||
get() {
|
||||
return this.workingFormStore.content
|
||||
},
|
||||
/* We add a setter */
|
||||
set(value) {
|
||||
this.workingFormStore.set(value)
|
||||
},
|
||||
},
|
||||
allTagsOptions() {
|
||||
return this.formsStore.allTags.map((tagname) => {
|
||||
return {
|
||||
name: tagname,
|
||||
value: tagname,
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
// Methods
|
||||
const copySettings = () => {
|
||||
if (copyFormId.value == null)
|
||||
return
|
||||
const copyForm = clonedeep(
|
||||
forms.value.find(form => form.id === copyFormId.value),
|
||||
)
|
||||
if (!copyForm)
|
||||
return;
|
||||
|
||||
watch: {},
|
||||
// Clean copy from form
|
||||
[
|
||||
"title",
|
||||
"properties",
|
||||
"cleanings",
|
||||
"views_count",
|
||||
"submissions_count",
|
||||
"workspace",
|
||||
"workspace_id",
|
||||
"updated_at",
|
||||
"share_url",
|
||||
"slug",
|
||||
"notion_database_url",
|
||||
"id",
|
||||
"database_id",
|
||||
"database_fields_update",
|
||||
"creator",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"last_edited_human",
|
||||
].forEach((property) => {
|
||||
if (_has(copyForm, property))
|
||||
delete copyForm[property]
|
||||
})
|
||||
|
||||
mounted() {},
|
||||
|
||||
methods: {
|
||||
copySettings() {
|
||||
if (this.copyFormId == null) return
|
||||
const copyForm = clonedeep(
|
||||
this.forms.find((form) => form.id === this.copyFormId),
|
||||
)
|
||||
if (!copyForm) return;
|
||||
|
||||
// Clean copy from form
|
||||
[
|
||||
"title",
|
||||
"properties",
|
||||
"cleanings",
|
||||
"views_count",
|
||||
"submissions_count",
|
||||
"workspace",
|
||||
"workspace_id",
|
||||
"updated_at",
|
||||
"share_url",
|
||||
"slug",
|
||||
"notion_database_url",
|
||||
"id",
|
||||
"database_id",
|
||||
"database_fields_update",
|
||||
"creator",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"last_edited_human",
|
||||
].forEach((property) => {
|
||||
if (_has(copyForm, property)) {
|
||||
delete copyForm[property]
|
||||
}
|
||||
})
|
||||
|
||||
// Apply changes
|
||||
Object.keys(copyForm).forEach((property) => {
|
||||
this.form[property] = copyForm[property]
|
||||
})
|
||||
this.showCopyFormSettingsModal = false
|
||||
},
|
||||
},
|
||||
// Apply changes
|
||||
Object.keys(copyForm).forEach((property) => {
|
||||
form.value[property] = copyForm[property]
|
||||
})
|
||||
showCopyFormSettingsModal.value = false
|
||||
useAlert().success('Form settings copied.')
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user