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:
JhumanJ 2025-05-16 19:58:53 +02:00
parent b5517c6fce
commit 1ba7805e35
8 changed files with 170 additions and 110 deletions

View File

@ -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()

View File

@ -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({

View File

@ -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"

View File

@ -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>

View 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>

22
client/lib/quill/quillPatches.js vendored Normal file
View File

@ -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 &nbsp; with regular spaces
.replace(/&nbsp;/g, ' ')
// 2. Fix line breaks by replacing empty paragraphs with paragraphs containing <br/>
.replace(/<p><\/p>/g, '<p><br/></p>')
}
})()
export default {}

View File

@ -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 = [
{ {

View File

@ -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({