Enhance Form Visibility Logic and UI Components
- Updated the `getIsClosedAttribute` method in `Form.php` to include a check for the form's visibility status, ensuring that forms marked as 'closed' are accurately reflected in their state. - Modified `QuillyEditor.vue` to import additional Quill patches for improved functionality. - Changed the alert color from yellow to amber in `OpenCompleteForm.vue` for better visual consistency. - Refactored the form status display in `OpenCompleteForm.vue` and `show.vue` to utilize the new `FormStatusBadges` component, streamlining the UI and improving maintainability. - Enhanced the `FormEditorNavbar.vue` to include icons for better user experience and clarity. These changes aim to improve the accuracy of form visibility handling and enhance the overall user interface across various components.
This commit is contained in:
parent
b5517c6fce
commit
1ba7805e35
|
|
@ -223,7 +223,7 @@ class Form extends Model implements CachableAttributes
|
||||||
|
|
||||||
public function getIsClosedAttribute()
|
public function getIsClosedAttribute()
|
||||||
{
|
{
|
||||||
return $this->closes_at && now()->gt($this->closes_at);
|
return $this->visibility === 'closed' || ($this->closes_at && now()->gt($this->closes_at));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFormPendingSubmissionKeyAttribute()
|
public function getFormPendingSubmissionKeyAttribute()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import Quill from 'quill'
|
import Quill from 'quill'
|
||||||
import 'quill/dist/quill.snow.css'
|
import 'quill/dist/quill.snow.css'
|
||||||
|
import '../../../lib/quill/quillPatches'
|
||||||
import { onMounted, onBeforeUnmount, ref, watch } from 'vue'
|
import { onMounted, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
class="m-2 my-4">
|
class="m-2 my-4">
|
||||||
<UAlert
|
<UAlert
|
||||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
||||||
color="yellow"
|
color="amber"
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
icon="i-material-symbols-info-outline"
|
icon="i-material-symbols-info-outline"
|
||||||
@close="hidePasswordDisabledMsg = true"
|
@close="hidePasswordDisabledMsg = true"
|
||||||
|
|
@ -64,29 +64,35 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div
|
<UAlert
|
||||||
v-if="isPublicFormPage && (form.is_closed || form.visibility=='closed')"
|
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
|
<div
|
||||||
class="mb-0 py-2 px-4 text-yellow-600"
|
class="py-2"
|
||||||
v-html="form.closed_text"
|
v-html="form.closed_text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</UAlert>
|
||||||
|
|
||||||
<div
|
<UAlert
|
||||||
v-else-if="isPublicFormPage && form.max_number_of_submissions_reached"
|
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
|
<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"
|
v-html="form.max_submissions_reached_text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</UAlert>
|
||||||
|
|
||||||
<form-cleanings
|
<form-cleanings
|
||||||
v-if="showFormCleanings"
|
v-if="showFormCleanings"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex-grow flex justify-center">
|
<div class="flex-grow flex justify-center gap-2">
|
||||||
<EditableTag
|
<EditableTag
|
||||||
id="form-editor-title"
|
id="form-editor-title"
|
||||||
v-model="form.title"
|
v-model="form.title"
|
||||||
|
|
@ -34,12 +34,14 @@
|
||||||
v-if="form.visibility == 'draft'"
|
v-if="form.visibility == 'draft'"
|
||||||
color="yellow"
|
color="yellow"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
|
icon="i-heroicons-pencil-square"
|
||||||
label="Draft"
|
label="Draft"
|
||||||
/>
|
/>
|
||||||
<UBadge
|
<UBadge
|
||||||
v-else-if="form.visibility == 'closed'"
|
v-else-if="form.visibility == 'closed'"
|
||||||
color="gray"
|
color="gray"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
|
icon="i-heroicons-lock-closed-20-solid"
|
||||||
label="Closed"
|
label="Closed"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Quill from 'quill'
|
||||||
|
|
||||||
|
// Self-executing function to patch Quill's prototype
|
||||||
|
;(function installQuillFixes() {
|
||||||
|
// Store the original method
|
||||||
|
const originalGetSemanticHTML = Quill.prototype.getSemanticHTML
|
||||||
|
|
||||||
|
// Override the getSemanticHTML method
|
||||||
|
Quill.prototype.getSemanticHTML = function(index = 0, length) {
|
||||||
|
// Call the original method
|
||||||
|
const html = originalGetSemanticHTML.call(this, index, length || this.getLength())
|
||||||
|
|
||||||
|
// Apply fixes:
|
||||||
|
return html
|
||||||
|
// 1. Replace with regular spaces
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
// 2. Fix line breaks by replacing empty paragraphs with paragraphs containing <br/>
|
||||||
|
.replace(/<p><\/p>/g, '<p><br/></p>')
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
export default {}
|
||||||
|
|
@ -78,60 +78,11 @@
|
||||||
</span>
|
</span>
|
||||||
<span>- Edited {{ form.last_edited_human }}</span>
|
<span>- Edited {{ form.last_edited_human }}</span>
|
||||||
</p>
|
</p>
|
||||||
<div
|
|
||||||
v-if="
|
<FormStatusBadges
|
||||||
['draft', 'closed'].includes(form.visibility) ||
|
:form="form"
|
||||||
(form.tags && form.tags.length > 0)
|
class="mt-2"
|
||||||
"
|
/>
|
||||||
class="mt-2 flex items-center flex-wrap gap-3"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="form.visibility == 'draft'"
|
|
||||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
Draft - not publicly accessible
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else-if="form.visibility == 'closed'"
|
|
||||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
Closed - won't accept new submissions
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-for="(tag) in form.tags"
|
|
||||||
:key="tag"
|
|
||||||
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
{{ tag }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p
|
|
||||||
v-if="form.closes_at"
|
|
||||||
class="text-yellow-500"
|
|
||||||
>
|
|
||||||
<span v-if="form.is_closed">
|
|
||||||
This form stopped accepting submissions on the
|
|
||||||
{{ displayClosesDate }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
This form will stop accepting submissions on the
|
|
||||||
{{ displayClosesDate }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
v-if="form.max_submissions_count > 0"
|
|
||||||
class="text-yellow-500"
|
|
||||||
>
|
|
||||||
<span v-if="form.max_number_of_submissions_reached">
|
|
||||||
The form is now closed because it reached its limit of
|
|
||||||
{{ form.max_submissions_count }} submissions.
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
This form will stop accepting submissions after
|
|
||||||
{{ form.max_submissions_count }} submissions.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form-cleanings
|
<form-cleanings
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
|
|
@ -181,6 +132,7 @@
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import ExtraMenu from "../../../components/pages/forms/show/ExtraMenu.vue"
|
import ExtraMenu from "../../../components/pages/forms/show/ExtraMenu.vue"
|
||||||
import FormCleanings from "../../../components/pages/forms/show/FormCleanings.vue"
|
import FormCleanings from "../../../components/pages/forms/show/FormCleanings.vue"
|
||||||
|
import FormStatusBadges from "../../../components/open/forms/components/FormStatusBadges.vue"
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "auth",
|
middleware: "auth",
|
||||||
|
|
@ -201,23 +153,6 @@ const form = computed(() => formsStore.getByKey(slug))
|
||||||
const workspace = computed(() => workspacesStore.getCurrent)
|
const workspace = computed(() => workspacesStore.getCurrent)
|
||||||
|
|
||||||
const loading = computed(() => formsStore.loading || workspacesStore.loading)
|
const loading = computed(() => formsStore.loading || workspacesStore.loading)
|
||||||
const displayClosesDate = computed(() => {
|
|
||||||
if (form.value && form.value.closes_at) {
|
|
||||||
const dateObj = new Date(form.value.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")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabsList = [
|
const tabsList = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -153,30 +153,13 @@
|
||||||
{{ form?.creator?.name }}
|
{{ form?.creator?.name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div
|
|
||||||
v-if="['draft','closed'].includes(form.visibility) || (form.tags && form.tags.length > 0)"
|
<FormStatusBadges
|
||||||
class="mt-1 flex items-center flex-wrap gap-3"
|
:form="form"
|
||||||
>
|
class="mt-1"
|
||||||
<span
|
size="xs"
|
||||||
v-if="form.visibility=='draft'"
|
/>
|
||||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
Draft
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else-if="form.visibility=='closed'"
|
|
||||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
Closed
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-for="(tag) in form.tags"
|
|
||||||
:key="tag"
|
|
||||||
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
{{ tag }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<extra-menu
|
<extra-menu
|
||||||
:form="form"
|
:form="form"
|
||||||
|
|
@ -238,6 +221,7 @@ import {useWorkspacesStore} from "../stores/workspaces"
|
||||||
import Fuse from "fuse.js"
|
import Fuse from "fuse.js"
|
||||||
import TextInput from "../components/forms/TextInput.vue"
|
import TextInput from "../components/forms/TextInput.vue"
|
||||||
import ExtraMenu from "../components/pages/forms/show/ExtraMenu.vue"
|
import ExtraMenu from "../components/pages/forms/show/ExtraMenu.vue"
|
||||||
|
import FormStatusBadges from "../components/open/forms/components/FormStatusBadges.vue"
|
||||||
import {refDebounced} from "@vueuse/core"
|
import {refDebounced} from "@vueuse/core"
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue