feat(#13): Show form elements depending on other form element values
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<template v-for="(formElement, index) in props.modelValue" :key="formElement.id">
|
||||
<template v-for="(formElement, index) in visibleFormElements" :key="formElement.id">
|
||||
<div class="group flex py-3 lg:py-4">
|
||||
<div class="flex-auto">
|
||||
<p v-if="formElement.title" class="font-semibold">{{ formElement.title }}</p>
|
||||
@@ -8,7 +8,7 @@
|
||||
:is="getResolvedComponent(formElement)"
|
||||
:form-options="formElement.options"
|
||||
:disabled="props.disabled"
|
||||
@update:form-options="updateFormOptions($event, index)"
|
||||
@update:form-options="updateFormOptions($event, formElement.id)"
|
||||
/>
|
||||
<TheComment
|
||||
v-if="applicationFormId && activeFormElement === formElement.id"
|
||||
@@ -32,7 +32,7 @@
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<USeparator v-if="index < props.modelValue.length - 1" />
|
||||
<USeparator v-if="index < visibleFormElements.length - 1" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -45,6 +45,7 @@ import { useCommentStore } from '~~/stores/useCommentStore'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: FormElementDto[]
|
||||
visibilityMap: Map<string, boolean>
|
||||
applicationFormId?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
@@ -68,6 +69,10 @@ const route = useRoute()
|
||||
const activeFormElement = ref('')
|
||||
const openDropdownId = ref<string | null>(null)
|
||||
|
||||
const visibleFormElements = computed(() => {
|
||||
return props.modelValue.filter((element) => props.visibilityMap.get(element.id) !== false)
|
||||
})
|
||||
|
||||
function handleDropdownToggle(formElementId: string, isOpen: boolean) {
|
||||
openDropdownId.value = isOpen ? formElementId : null
|
||||
}
|
||||
@@ -112,14 +117,15 @@ function getDropdownItems(formElementId: string, formElementPosition: number): D
|
||||
return [items]
|
||||
}
|
||||
|
||||
function updateFormOptions(formOptions: FormOptionDto[], formElementIndex: number) {
|
||||
console.log('Updating form options for element index:', formElementIndex, formOptions)
|
||||
const updatedModelValue = [...props.modelValue]
|
||||
const currentElement = updatedModelValue[formElementIndex]
|
||||
if (currentElement) {
|
||||
updatedModelValue[formElementIndex] = { ...currentElement, options: formOptions }
|
||||
emit('update:modelValue', updatedModelValue)
|
||||
}
|
||||
function updateFormOptions(formOptions: FormOptionDto[], formElementId: string) {
|
||||
console.log('Updating form options for element ID:', formElementId, formOptions)
|
||||
const updatedModelValue = props.modelValue.map((element) => {
|
||||
if (element.id === formElementId) {
|
||||
return { ...element, options: formOptions }
|
||||
}
|
||||
return element
|
||||
})
|
||||
emit('update:modelValue', updatedModelValue)
|
||||
}
|
||||
|
||||
function toggleComments(formElementId: string) {
|
||||
|
||||
@@ -8,25 +8,31 @@
|
||||
{{ currentFormElementSection.title }}
|
||||
</h1>
|
||||
|
||||
<template v-if="currentFormElementSection?.formElementSubSections">
|
||||
<UCard
|
||||
v-for="subsection in currentFormElementSection.formElementSubSections"
|
||||
:key="subsection.id"
|
||||
variant="subtle"
|
||||
class="mb-6"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<h2 class="text-lg font-semibold text-highlighted">{{ subsection.title }}</h2>
|
||||
<p v-if="subsection.subtitle" class="text-sm text-dimmed">{{ subsection.subtitle }}</p>
|
||||
</div>
|
||||
<UCard
|
||||
v-for="subsection in visibleSubsections"
|
||||
:key="subsection.id"
|
||||
variant="subtle"
|
||||
class="mb-6"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<h2 class="text-lg font-semibold text-highlighted">{{ subsection.title }}</h2>
|
||||
<p v-if="subsection.subtitle" class="text-sm text-dimmed">{{ subsection.subtitle }}</p>
|
||||
</div>
|
||||
<FormEngine
|
||||
v-model="subsection.formElements"
|
||||
:visibility-map="visibilityMap"
|
||||
:application-form-id="applicationFormId"
|
||||
:disabled="disabled"
|
||||
@add:input-form="(position) => handleAddInputForm(position, subsection.id)"
|
||||
/>
|
||||
</UCard>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<UCard v-if="visibleSubsections.length === 0" variant="subtle" class="mb-6">
|
||||
<div class="text-center py-8 text-dimmed">
|
||||
<UIcon name="i-lucide-eye-off" class="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p>{{ $t('applicationForms.noVisibleElements') }}</p>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<div class="flex gap-2 justify-between">
|
||||
<UButton leading-icon="i-lucide-arrow-left" :disabled="!stepper?.hasPrev" @click="handleNavigate('backward')">
|
||||
@@ -76,6 +82,30 @@ const { stepper, activeStepperItemIndex, stepperItems, currentFormElementSection
|
||||
computed(() => props.formElementSections)
|
||||
)
|
||||
|
||||
const allFormElements = computed(() => {
|
||||
return props.formElementSections.flatMap(section =>
|
||||
section.formElementSubSections?.flatMap(subsection =>
|
||||
subsection.formElements
|
||||
) ?? []
|
||||
)
|
||||
})
|
||||
|
||||
const { evaluateVisibility } = useFormElementVisibility()
|
||||
|
||||
const visibilityMap = computed(() => {
|
||||
return evaluateVisibility(allFormElements.value)
|
||||
})
|
||||
|
||||
const visibleSubsections = computed(() => {
|
||||
if (!currentFormElementSection.value?.formElementSubSections) {
|
||||
return []
|
||||
}
|
||||
|
||||
return currentFormElementSection.value.formElementSubSections.filter(subsection => {
|
||||
return subsection.formElements.some(element => visibilityMap.value.get(element.id) !== false)
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.initialSectionIndex !== undefined) {
|
||||
activeStepperItemIndex.value = props.initialSectionIndex
|
||||
|
||||
@@ -13,10 +13,17 @@ export function useApplicationFormValidator() {
|
||||
return Object.values(ComplianceStatus)[highestComplianceNumber] ?? ComplianceStatus.NonCritical
|
||||
}
|
||||
|
||||
function validateFormElements(formElements: FormElementDto[]): Map<FormElementId, ComplianceStatus> {
|
||||
function validateFormElements(
|
||||
formElements: FormElementDto[],
|
||||
visibilityMap?: Map<string, boolean>
|
||||
): Map<FormElementId, ComplianceStatus> {
|
||||
formElementComplianceMap.value.clear()
|
||||
|
||||
formElements.forEach((formElement) => {
|
||||
if (visibilityMap && visibilityMap.get(formElement.id) === false) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!complianceCheckableElementTypes.includes(formElement.type)) return
|
||||
|
||||
// Reset any previously set compliance status when all options are false
|
||||
|
||||
83
legalconsenthub/app/composables/useFormElementVisibility.ts
Normal file
83
legalconsenthub/app/composables/useFormElementVisibility.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { FormElementDto, VisibilityConditionOperator } from '~~/.api-client'
|
||||
import { VisibilityConditionOperator as VCOperator, VisibilityConditionType as VCType } from '~~/.api-client'
|
||||
|
||||
export function useFormElementVisibility() {
|
||||
function evaluateVisibility(allFormElements: FormElementDto[]): Map<string, boolean> {
|
||||
const formElementsByRef = buildFormElementsMap(allFormElements)
|
||||
const visibilityMap = new Map<string, boolean>()
|
||||
|
||||
allFormElements.forEach((element) => {
|
||||
const isVisible = isElementVisible(element, formElementsByRef, visibilityMap)
|
||||
visibilityMap.set(element.id, isVisible)
|
||||
})
|
||||
|
||||
return visibilityMap
|
||||
}
|
||||
|
||||
function buildFormElementsMap(formElements: FormElementDto[]): Map<string, FormElementDto> {
|
||||
const map = new Map<string, FormElementDto>()
|
||||
formElements.forEach((element) => {
|
||||
if (element.reference) {
|
||||
map.set(element.reference, element)
|
||||
}
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
function isElementVisible(
|
||||
element: FormElementDto,
|
||||
formElementsByRef: Map<string, FormElementDto>,
|
||||
_visibilityMap: Map<string, boolean>
|
||||
): boolean {
|
||||
if (!element.visibilityCondition) {
|
||||
return true
|
||||
}
|
||||
|
||||
const condition = element.visibilityCondition
|
||||
|
||||
const sourceElement = formElementsByRef.get(condition.sourceFormElementReference)
|
||||
if (!sourceElement) {
|
||||
return false
|
||||
}
|
||||
|
||||
const sourceValue = getFormElementValue(sourceElement)
|
||||
|
||||
const operator = condition.operator || VCOperator.Equals
|
||||
const conditionMet = evaluateCondition(sourceValue, condition.expectedValue, operator)
|
||||
|
||||
return condition.conditionType === VCType.Show ? conditionMet : !conditionMet
|
||||
}
|
||||
|
||||
function getFormElementValue(element: FormElementDto): string {
|
||||
const selectedOption = element.options.find((option) => option.value === 'true')
|
||||
return selectedOption?.label || ''
|
||||
}
|
||||
|
||||
function evaluateCondition(
|
||||
actualValue: string,
|
||||
expectedValue: string,
|
||||
operator: VisibilityConditionOperator
|
||||
): boolean {
|
||||
let result: boolean
|
||||
switch (operator) {
|
||||
case VCOperator.Equals:
|
||||
result = actualValue.toLowerCase() === expectedValue.toLowerCase()
|
||||
return result
|
||||
case VCOperator.NotEquals:
|
||||
result = actualValue.toLowerCase() !== expectedValue.toLowerCase()
|
||||
return result
|
||||
case VCOperator.IsEmpty:
|
||||
result = actualValue === ''
|
||||
return result
|
||||
case VCOperator.IsNotEmpty:
|
||||
result = actualValue !== ''
|
||||
return result
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
evaluateVisibility
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,7 @@ const {
|
||||
|
||||
const { updateApplicationForm: updateForm, submitApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { evaluateVisibility } = useFormElementVisibility()
|
||||
const { canWriteApplicationForms } = usePermissions()
|
||||
const userStore = useUserStore()
|
||||
const { user } = storeToRefs(userStore)
|
||||
@@ -100,10 +101,14 @@ const allFormElements = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const visibilityMap = computed(() => {
|
||||
return evaluateVisibility(allFormElements.value)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => allFormElements.value,
|
||||
(updatedFormElements: FormElementDto[]) => {
|
||||
validationMap.value = validateFormElements(updatedFormElements)
|
||||
validationMap.value = validateFormElements(updatedFormElements, visibilityMap.value)
|
||||
validationStatus.value = getHighestComplianceStatus()
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
@@ -53,6 +53,7 @@ import { useUserStore } from '~~/stores/useUserStore'
|
||||
const { getAllApplicationFormTemplates } = await useApplicationFormTemplate()
|
||||
const { createApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { evaluateVisibility } = useFormElementVisibility()
|
||||
const { canWriteApplicationForms } = usePermissions()
|
||||
const userStore = useUserStore()
|
||||
const { selectedOrganization } = storeToRefs(userStore)
|
||||
@@ -87,10 +88,14 @@ const allFormElements = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const visibilityMap = computed(() => {
|
||||
return evaluateVisibility(allFormElements.value)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => allFormElements.value,
|
||||
(updatedFormElements: FormElementDto[]) => {
|
||||
validationMap.value = validateFormElements(updatedFormElements)
|
||||
validationMap.value = validateFormElements(updatedFormElements, visibilityMap.value)
|
||||
validationStatus.value = getHighestComplianceStatus()
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"noPermission": "Keine Berechtigung",
|
||||
"noPermissionDescription": "Sie haben keine Berechtigung zum Erstellen von Anträgen.",
|
||||
"backToOverview": "Zurück zur Übersicht",
|
||||
"noVisibleElements": "Alle Formularelemente in diesem Abschnitt sind ausgeblendet",
|
||||
"deleteConfirm": "Möchten Sie wirklich den Mitbestimmungsantrag \"{name}\" löschen?",
|
||||
"deleteTitle": "Mitbestimmungsantrag löschen",
|
||||
"lastEditedBy": "Zuletzt bearbeitet von",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"noPermission": "No Permission",
|
||||
"noPermissionDescription": "You do not have permission to create applications.",
|
||||
"backToOverview": "Back to Overview",
|
||||
"noVisibleElements": "All form elements in this section are hidden",
|
||||
"deleteConfirm": "Do you really want to delete the co-determination application \"{name}\"?",
|
||||
"deleteTitle": "Delete Co-determination Application",
|
||||
"lastEditedBy": "Last edited by",
|
||||
|
||||
Reference in New Issue
Block a user