feat(fullstack): Add form element section and stepper
This commit is contained in:
@@ -665,7 +665,7 @@ components:
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- formElements
|
||||
- formElementSections
|
||||
- isTemplate
|
||||
- organizationId
|
||||
- createdBy
|
||||
@@ -678,10 +678,10 @@ components:
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
formElements:
|
||||
formElementSections:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FormElementDto"
|
||||
$ref: "#/components/schemas/FormElementSectionDto"
|
||||
isTemplate:
|
||||
type: boolean
|
||||
organizationId:
|
||||
@@ -700,16 +700,16 @@ components:
|
||||
CreateApplicationFormDto:
|
||||
required:
|
||||
- name
|
||||
- formElements
|
||||
- formElementSections
|
||||
- isTemplate
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
formElements:
|
||||
formElementSections:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CreateFormElementDto"
|
||||
$ref: "#/components/schemas/CreateFormElementSectionDto"
|
||||
isTemplate:
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -728,21 +728,64 @@ components:
|
||||
items:
|
||||
$ref: "#/components/schemas/ApplicationFormDto"
|
||||
|
||||
|
||||
####### Form #######
|
||||
FormElementDto:
|
||||
FormElementSectionDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- title
|
||||
- formElements
|
||||
- applicationFormId
|
||||
- options
|
||||
- type
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
shortTitle:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
formElements:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FormElementDto"
|
||||
applicationFormId:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
CreateFormElementSectionDto:
|
||||
type: object
|
||||
required:
|
||||
- title
|
||||
- formElements
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
shortTitle:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
formElements:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CreateFormElementDto"
|
||||
|
||||
FormElementDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- options
|
||||
- type
|
||||
- formElementSectionId
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
@@ -753,6 +796,9 @@ components:
|
||||
$ref: "#/components/schemas/FormOptionDto"
|
||||
type:
|
||||
$ref: "#/components/schemas/FormElementType"
|
||||
formElementSectionId:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
CreateFormElementDto:
|
||||
type: object
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.application_form
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElement
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import jakarta.persistence.AttributeOverride
|
||||
import jakarta.persistence.AttributeOverrides
|
||||
@@ -29,7 +29,7 @@ class ApplicationForm(
|
||||
var name: String = "",
|
||||
|
||||
@OneToMany(mappedBy = "applicationForm", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||
var formElements: MutableList<FormElement> = mutableListOf(),
|
||||
var formElementSections: MutableList<FormElementSection> = mutableListOf(),
|
||||
|
||||
@Column(nullable = false)
|
||||
var isTemplate: Boolean,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.application_form
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSectionMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserMapper
|
||||
@@ -11,12 +11,12 @@ import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class ApplicationFormMapper(private val formElementMapper: FormElementMapper, private val userMapper: UserMapper) {
|
||||
class ApplicationFormMapper(private val formElementSectionMapper: FormElementSectionMapper, private val userMapper: UserMapper) {
|
||||
fun toApplicationFormDto(applicationForm: ApplicationForm): ApplicationFormDto {
|
||||
return ApplicationFormDto(
|
||||
id = applicationForm.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"),
|
||||
name = applicationForm.name,
|
||||
formElements = applicationForm.formElements.map { formElementMapper.toFormElementDto(it) },
|
||||
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSectionDto(it) },
|
||||
isTemplate = applicationForm.isTemplate,
|
||||
organizationId = applicationForm.organizationId,
|
||||
createdBy = userMapper.toUserDto(applicationForm.createdBy),
|
||||
@@ -30,7 +30,7 @@ class ApplicationFormMapper(private val formElementMapper: FormElementMapper, pr
|
||||
return ApplicationForm(
|
||||
id = applicationForm.id,
|
||||
name = applicationForm.name,
|
||||
formElements = applicationForm.formElements.map { formElementMapper.toFormElement(it) }.toMutableList(),
|
||||
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSection(it) }.toMutableList(),
|
||||
isTemplate = applicationForm.isTemplate,
|
||||
organizationId = applicationForm.organizationId,
|
||||
createdBy = userMapper.toUser(applicationForm.createdBy),
|
||||
@@ -53,8 +53,8 @@ class ApplicationFormMapper(private val formElementMapper: FormElementMapper, pr
|
||||
createdBy = createdBy,
|
||||
lastModifiedBy = lastModifiedBy,
|
||||
)
|
||||
applicationForm.formElements = createApplicationFormDto.formElements
|
||||
.map { formElementMapper.toFormElement(it, applicationForm) }
|
||||
applicationForm.formElementSections = createApplicationFormDto.formElementSections
|
||||
.map { formElementSectionMapper.toFormElementSection(it, applicationForm) }
|
||||
.toMutableList()
|
||||
return applicationForm
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.error
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
class FormElementSectionNotFoundException(id: UUID): RuntimeException("Couldn't find form element section with ID: $id")
|
||||
@@ -1,18 +1,16 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element;
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||
import com.betriebsratkanzlei.legalconsenthub.comment.Comment
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementType
|
||||
import jakarta.persistence.CascadeType
|
||||
import jakarta.persistence.CollectionTable
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.ElementCollection
|
||||
import jakarta.persistence.Embeddable
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn
|
||||
import jakarta.persistence.ManyToOne
|
||||
import jakarta.persistence.OneToMany
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -22,10 +20,6 @@ class FormElement(
|
||||
@GeneratedValue
|
||||
var id: UUID? = null,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "application_form_id", nullable = false)
|
||||
var applicationForm: ApplicationForm? = null,
|
||||
|
||||
var title: String? = null,
|
||||
|
||||
var description: String? = null,
|
||||
@@ -35,5 +29,9 @@ class FormElement(
|
||||
var options: MutableList<FormOption> = mutableListOf(),
|
||||
|
||||
@Column(nullable = false)
|
||||
var type: FormElementType
|
||||
var type: FormElementType,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "form_element_section_id", nullable = false)
|
||||
var formElementSection: FormElementSection? = null
|
||||
)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormRepository
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.FormElementSectionNotFoundException
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementDto
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -10,7 +8,7 @@ import org.springframework.stereotype.Component
|
||||
@Component
|
||||
class FormElementMapper(
|
||||
private val formOptionMapper: FormOptionMapper,
|
||||
private val applicationFormRepository: ApplicationFormRepository
|
||||
private val formElementSectionRepository: FormElementSectionRepository
|
||||
) {
|
||||
fun toFormElementDto(formElement: FormElement): FormElementDto {
|
||||
return FormElementDto(
|
||||
@@ -19,14 +17,14 @@ class FormElementMapper(
|
||||
description = formElement.description,
|
||||
options = formElement.options.map { formOptionMapper.toFormOptionDto(it) },
|
||||
type = formElement.type,
|
||||
applicationFormId = formElement.applicationForm?.id
|
||||
?: throw IllegalStateException("ApplicationForm ID must not be null!")
|
||||
formElementSectionId = formElement.formElementSection?.id
|
||||
?: throw IllegalStateException("FormElementSection ID must not be null!")
|
||||
)
|
||||
}
|
||||
|
||||
fun toFormElement(formElement: FormElementDto): FormElement {
|
||||
val applicationForm = applicationFormRepository.findById(formElement.applicationFormId)
|
||||
.orElseThrow { ApplicationFormNotFoundException(formElement.applicationFormId) }
|
||||
val formElementSection = formElementSectionRepository.findById(formElement.formElementSectionId)
|
||||
.orElseThrow { FormElementSectionNotFoundException(formElement.formElementSectionId) }
|
||||
|
||||
return FormElement(
|
||||
id = formElement.id,
|
||||
@@ -34,18 +32,18 @@ class FormElementMapper(
|
||||
description = formElement.description,
|
||||
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
|
||||
type = formElement.type,
|
||||
applicationForm = applicationForm
|
||||
formElementSection = formElementSection
|
||||
)
|
||||
}
|
||||
|
||||
fun toFormElement(formElement: CreateFormElementDto, applicationForm: ApplicationForm): FormElement {
|
||||
fun toFormElement(formElement: CreateFormElementDto, formElementSection: FormElementSection): FormElement {
|
||||
return FormElement(
|
||||
id = null,
|
||||
title = formElement.title,
|
||||
description = formElement.description,
|
||||
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
|
||||
type = formElement.type,
|
||||
applicationForm = applicationForm
|
||||
formElementSection = formElementSection
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element;
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||
import jakarta.persistence.CascadeType
|
||||
import jakarta.persistence.CollectionTable
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.ElementCollection
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn
|
||||
import jakarta.persistence.ManyToOne
|
||||
import jakarta.persistence.OneToMany
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
class FormElementSection(
|
||||
@Id
|
||||
@GeneratedValue
|
||||
var id: UUID? = null,
|
||||
|
||||
@Column(nullable = false)
|
||||
var title: String,
|
||||
|
||||
var shortTitle: String? = null,
|
||||
|
||||
var description: String? = null,
|
||||
|
||||
@OneToMany(mappedBy = "formElementSection", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||
var formElements: MutableList<FormElement> = mutableListOf(),
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "application_form_id", nullable = false)
|
||||
var applicationForm: ApplicationForm? = null,
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationFormRepository
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementSectionDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementSectionDto
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class FormElementSectionMapper(
|
||||
private val formElementMapper: FormElementMapper,
|
||||
private val applicationFormRepository: ApplicationFormRepository
|
||||
) {
|
||||
fun toFormElementSectionDto(formElementSection: FormElementSection): FormElementSectionDto {
|
||||
return FormElementSectionDto(
|
||||
id = formElementSection.id ?: throw IllegalStateException("FormElementSection ID must not be null!"),
|
||||
title = formElementSection.title,
|
||||
description = formElementSection.description,
|
||||
shortTitle = formElementSection.shortTitle,
|
||||
formElements = formElementSection.formElements.map { formElementMapper.toFormElementDto(it) },
|
||||
applicationFormId = formElementSection.applicationForm?.id
|
||||
?: throw IllegalStateException("ApplicationForm ID must not be null!")
|
||||
)
|
||||
}
|
||||
|
||||
fun toFormElementSection(formElementSection: FormElementSectionDto): FormElementSection {
|
||||
val applicationForm = applicationFormRepository.findById(formElementSection.applicationFormId)
|
||||
.orElseThrow { ApplicationFormNotFoundException(formElementSection.applicationFormId) }
|
||||
|
||||
return FormElementSection(
|
||||
id = formElementSection.id,
|
||||
title = formElementSection.title,
|
||||
description = formElementSection.description,
|
||||
shortTitle = formElementSection.shortTitle,
|
||||
formElements = formElementSection.formElements.map { formElementMapper.toFormElement(it) }.toMutableList(),
|
||||
applicationForm = applicationForm
|
||||
)
|
||||
}
|
||||
|
||||
fun toFormElementSection(createFormElementSection: CreateFormElementSectionDto, applicationForm: ApplicationForm): FormElementSection {
|
||||
val formElementSection = FormElementSection(
|
||||
title = createFormElementSection.title,
|
||||
description = createFormElementSection.description,
|
||||
shortTitle = createFormElementSection.shortTitle,
|
||||
applicationForm = applicationForm
|
||||
)
|
||||
formElementSection.formElements = createFormElementSection.formElements
|
||||
.map { formElementMapper.toFormElement(it, formElementSection) }
|
||||
.toMutableList()
|
||||
return formElementSection
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.form_element
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.util.UUID
|
||||
|
||||
@Repository
|
||||
interface FormElementSectionRepository : JpaRepository<FormElementSection, UUID>
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormElementDto, FormOptionDto } from '~/.api-client'
|
||||
import type { CommentDto, FormElementDto, FormOptionDto } from '~/.api-client'
|
||||
import { useComment } from '~/composables/comment/useComment'
|
||||
import { resolveComponent } from 'vue'
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<UDashboardPanel id="home">
|
||||
<template #header>
|
||||
<UDashboardNavbar title="Home" :ui="{ right: 'gap-3' }">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UDropdownMenu :items="items">
|
||||
<UButton icon="i-lucide-plus" size="md" class="rounded-full" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #left> toolbar left </template>
|
||||
</UDashboardToolbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col w-full lg:max-w-4xl mx-auto">
|
||||
<UCard variant="subtle">
|
||||
<FormEngine
|
||||
v-if="applicationForm"
|
||||
v-model="applicationForm.formElements"
|
||||
:application-form-id="applicationForm.id"
|
||||
:disabled="isReadOnly"
|
||||
@click:comments="openComments"
|
||||
/>
|
||||
<UButton :disabled="isReadOnly" class="my-3 lg:my-4" @click="onSubmit">Submit</UButton>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ApplicationFormDto } from '~/.api-client'
|
||||
|
||||
const { getApplicationFormById, updateApplicationForm } = useApplicationForm()
|
||||
const route = useRoute()
|
||||
const { user } = useAuth()
|
||||
|
||||
const items = [
|
||||
[
|
||||
{
|
||||
label: 'Neuer Mitbestimmungsantrag',
|
||||
icon: 'i-lucide-send',
|
||||
to: '/create'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
const { data } = await useAsyncData<ApplicationFormDto>(async () => {
|
||||
return await getApplicationFormById(Array.isArray(route.params.id) ? route.params.id[0] : route.params.id)
|
||||
})
|
||||
|
||||
const applicationForm = computed({
|
||||
get: () => data?.value,
|
||||
set: (val) => {
|
||||
if (val && data.value) {
|
||||
data.value = val
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isReadOnly = computed(() => {
|
||||
return applicationForm.value?.createdBy.id !== user.value?.id
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
if (data?.value) {
|
||||
await updateApplicationForm(data.value.id, data.value)
|
||||
await navigateTo('/')
|
||||
}
|
||||
}
|
||||
|
||||
function openComments(formElementId: string) {
|
||||
console.log('open comments for', formElementId)
|
||||
}
|
||||
</script>
|
||||
159
legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue
Normal file
159
legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<UDashboardPanel id="home">
|
||||
<template #header>
|
||||
<UDashboardNavbar title="Home" :ui="{ right: 'gap-3' }">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UDropdownMenu :items="items">
|
||||
<UButton icon="i-lucide-plus" size="md" class="rounded-full" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #left> toolbar left </template>
|
||||
</UDashboardToolbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col w-full lg:max-w-4xl mx-auto">
|
||||
<UStepper ref="stepper" v-model="activeStepperItemIndex" :items="stepperItems" class="w-full" />
|
||||
<h1 class="text-xl text-pretty font-bold text-highlighted">
|
||||
{{ currentFormElementSection.title }}
|
||||
</h1>
|
||||
<UCard variant="subtle">
|
||||
<FormEngine
|
||||
v-if="applicationForm"
|
||||
v-model="currentFormElementSection.formElements"
|
||||
:application-form-id="applicationForm.id"
|
||||
:disabled="isReadOnly"
|
||||
@click:comments="openComments"
|
||||
/>
|
||||
<div class="flex gap-2 justify-between mt-4">
|
||||
<UButton
|
||||
leading-icon="i-lucide-arrow-left"
|
||||
:disabled="!stepper?.hasPrev"
|
||||
@click="navigateStepper('backward')"
|
||||
>
|
||||
Prev
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="stepper?.hasNext"
|
||||
trailing-icon="i-lucide-arrow-right"
|
||||
:disabled="!stepper?.hasNext"
|
||||
@click="navigateStepper('forward')"
|
||||
>
|
||||
Next
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="!stepper?.hasNext"
|
||||
trailing-icon="i-lucide-send-horizontal"
|
||||
:disabled="isReadOnly"
|
||||
@click="onSubmit"
|
||||
>
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ApplicationFormDto, FormElementSectionDto } from '~/.api-client'
|
||||
import type { StepperItem } from '@nuxt/ui'
|
||||
|
||||
const { getApplicationFormById, updateApplicationForm } = useApplicationForm()
|
||||
const route = useRoute()
|
||||
const { user } = useAuth()
|
||||
|
||||
const items = [
|
||||
[
|
||||
{
|
||||
label: 'Neuer Mitbestimmungsantrag',
|
||||
icon: 'i-lucide-send',
|
||||
to: '/create'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
const stepper = useTemplateRef('stepper')
|
||||
const activeStepperItemIndex = ref<number>(0)
|
||||
|
||||
const currentFormElementSection = computed<FormElementSectionDto>(() => {
|
||||
return applicationForm.value?.formElementSections[activeStepperItemIndex.value]
|
||||
})
|
||||
|
||||
watch(activeStepperItemIndex, async (newActiveStepperItem: number) => {
|
||||
activeStepperItemIndex.value = newActiveStepperItem
|
||||
await navigateTo(`/application-forms/${route.params.id}/${newActiveStepperItem}`)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(_) => {
|
||||
const sectionIndex = parseInt(route.params.sectionIndex[0])
|
||||
activeStepperItemIndex.value = !isNaN(sectionIndex) ? sectionIndex : 0
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const { data, error } = await useAsyncData<ApplicationFormDto>(async () => {
|
||||
return await getApplicationFormById(Array.isArray(route.params.id) ? route.params.id[0] : route.params.id)
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
throw createError({ statusText: error.value.message })
|
||||
}
|
||||
|
||||
const applicationForm = computed<ApplicationFormDto>({
|
||||
get: () => data?.value as ApplicationFormDto,
|
||||
set: (val: ApplicationFormDto) => {
|
||||
if (val && data.value) {
|
||||
data.value = val
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isReadOnly = computed(() => {
|
||||
return applicationForm.value?.createdBy.id !== user.value?.id
|
||||
})
|
||||
|
||||
const stepperItems = computed(() => {
|
||||
const stepperItems: StepperItem[] = []
|
||||
applicationForm.value.formElementSections.forEach((section: FormElementSectionDto, index: number, _) => {
|
||||
stepperItems.push({
|
||||
title: section.shortTitle,
|
||||
description: section.description
|
||||
})
|
||||
})
|
||||
return stepperItems
|
||||
})
|
||||
|
||||
async function navigateStepper(direction: 'forward' | 'backward') {
|
||||
if (direction === 'forward') {
|
||||
stepper.value?.next()
|
||||
} else {
|
||||
stepper.value?.prev()
|
||||
}
|
||||
const targetSectionIndex =
|
||||
direction === 'forward' ? activeStepperItemIndex.value + 1 : activeStepperItemIndex.value - 1
|
||||
await navigateTo(`/application-forms/${route.params.id}/${targetSectionIndex}`)
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (data?.value) {
|
||||
await updateApplicationForm(data.value.id, data.value)
|
||||
await navigateTo('/')
|
||||
}
|
||||
}
|
||||
|
||||
function openComments(formElementId: string) {
|
||||
console.log('open comments for', formElementId)
|
||||
}
|
||||
</script>
|
||||
@@ -26,8 +26,33 @@
|
||||
<UFormField label="Name">
|
||||
<UInput v-if="applicationFormTemplate" v-model="applicationFormTemplate.name" />
|
||||
</UFormField>
|
||||
<FormEngine v-model="formElements" />
|
||||
<UButton type="submit">Submit</UButton>
|
||||
<UStepper ref="stepper" v-model="activeStepperItem" :items="stepperItems" class="w-full" />
|
||||
<h1 v-if="currentFormElementSection?.title" class="text-xl text-pretty font-bold text-highlighted">
|
||||
{{ currentFormElementSection.title }}
|
||||
</h1>
|
||||
<FormEngine
|
||||
v-if="currentFormElementSection?.formElements"
|
||||
v-model="currentFormElementSection.formElements"
|
||||
/>
|
||||
<div class="flex gap-2 justify-between mt-4">
|
||||
<UButton
|
||||
leading-icon="i-lucide-arrow-left"
|
||||
:disabled="!stepper?.hasPrev"
|
||||
@click="navigateStepper('backward')"
|
||||
>
|
||||
Prev
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-if="stepper?.hasNext"
|
||||
trailing-icon="i-lucide-arrow-right"
|
||||
:disabled="!stepper?.hasNext"
|
||||
@click="navigateStepper('forward')"
|
||||
>
|
||||
Next
|
||||
</UButton>
|
||||
<UButton v-if="!stepper?.hasNext" @click="onSubmit"> Submit </UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</UPageCard>
|
||||
</div>
|
||||
@@ -36,20 +61,60 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComplianceStatus, type PagedApplicationFormDto } from '~/.api-client'
|
||||
import { ComplianceStatus, type FormElementSectionDto, type PagedApplicationFormDto } from '~/.api-client'
|
||||
import { useApplicationFormValidator } from '~/composables/useApplicationFormValidator'
|
||||
import type { FormElementId } from '~/types/FormElement'
|
||||
import type { StepperItem } from '@nuxt/ui'
|
||||
|
||||
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
||||
const { createApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { userDto, selectedOrganization } = useAuth()
|
||||
|
||||
const { data } = await useAsyncData<PagedApplicationFormDto>(async () => {
|
||||
const stepper = useTemplateRef('stepper')
|
||||
const activeStepperItem = ref<number>(0)
|
||||
|
||||
const currentFormElementSection = computed(() => {
|
||||
return applicationFormTemplate.value?.formElementSections[activeStepperItem.value]
|
||||
})
|
||||
|
||||
watch(activeStepperItem, async (newActiveStepperItem: number) => {
|
||||
activeStepperItem.value = newActiveStepperItem
|
||||
})
|
||||
|
||||
const { data, error } = await useAsyncData<PagedApplicationFormDto>(async () => {
|
||||
return await getAllApplicationFormTemplates()
|
||||
})
|
||||
|
||||
if (error.value) {
|
||||
throw createError({ statusText: error.value.message })
|
||||
}
|
||||
|
||||
const stepperItems = computed(() => {
|
||||
const stepperItems: StepperItem[] = []
|
||||
if (!applicationFormTemplate.value) {
|
||||
return stepperItems
|
||||
}
|
||||
|
||||
applicationFormTemplate.value.formElementSections.forEach((section: FormElementSectionDto) => {
|
||||
stepperItems.push({
|
||||
title: section.shortTitle,
|
||||
description: section.description
|
||||
})
|
||||
})
|
||||
return stepperItems
|
||||
})
|
||||
|
||||
async function navigateStepper(direction: 'forward' | 'backward') {
|
||||
if (direction === 'forward') {
|
||||
stepper.value?.next()
|
||||
} else {
|
||||
stepper.value?.prev()
|
||||
}
|
||||
}
|
||||
|
||||
const applicationFormTemplate = computed({
|
||||
// TODO: Don't select always the first item, allow user to select a template
|
||||
get: () => data?.value?.content[0] ?? undefined,
|
||||
set: (val) => {
|
||||
if (val && data.value) {
|
||||
@@ -59,10 +124,11 @@ const applicationFormTemplate = computed({
|
||||
})
|
||||
|
||||
const formElements = computed({
|
||||
get: () => applicationFormTemplate.value?.formElements ?? [],
|
||||
get: () => currentFormElementSection?.value?.formElements ?? [],
|
||||
set: (val) => {
|
||||
if (val && applicationFormTemplate.value) {
|
||||
applicationFormTemplate.value.formElements = val
|
||||
if (!currentFormElementSection.value) return
|
||||
currentFormElementSection.value.formElements = val
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
v-for="(applicationFormElem, index) in applicationForms"
|
||||
:key="applicationFormElem.id"
|
||||
class="flex justify-between items-center p-4 bg-white rounded-lg shadow-md"
|
||||
@click="navigateTo(`application-forms/${applicationFormElem.id}`)"
|
||||
@click="navigateTo(`application-forms/${applicationFormElem.id}/0`)"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-(--ui-text-highlighted) text-base">
|
||||
|
||||
154
testdata.json
154
testdata.json
@@ -3,83 +3,97 @@
|
||||
"name": "",
|
||||
"createdBy": "Denis",
|
||||
"lastModifiedBy": "Denis",
|
||||
"formElements": [
|
||||
"formElementSections": [
|
||||
{
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
"title": "Section 1",
|
||||
"shortTitle": "S1",
|
||||
"description": "First section of the form",
|
||||
"formElements": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (schwerwiegend)",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
}
|
||||
],
|
||||
"type": "SWITCH"
|
||||
},
|
||||
{
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (keine Auswirkungen)",
|
||||
"processingPurpose": "NONE",
|
||||
"employeeDataCategory": "NONE"
|
||||
}
|
||||
],
|
||||
"type": "SWITCH"
|
||||
},
|
||||
{
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (Mittel)",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
}
|
||||
],
|
||||
"type": "CHECKBOX"
|
||||
},
|
||||
{
|
||||
"title": "Eine weitere Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (schwerwiegend)",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
}
|
||||
],
|
||||
"type": "SWITCH"
|
||||
},
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Ablehnen",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
}
|
||||
],
|
||||
"type": "SELECT"
|
||||
},
|
||||
{
|
||||
"title": "Eine weitere Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (keine Auswirkungen)",
|
||||
"processingPurpose": "NONE",
|
||||
"employeeDataCategory": "NONE"
|
||||
}
|
||||
],
|
||||
"type": "SWITCH"
|
||||
},
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Ablehnen",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
"title": "Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen (Mittel)",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
}
|
||||
],
|
||||
"type": "CHECKBOX"
|
||||
}
|
||||
],
|
||||
"type": "RADIOBUTTON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Section 2",
|
||||
"shortTitle": "S2",
|
||||
"description": "Second section of the form",
|
||||
"formElements": [
|
||||
{
|
||||
"title": "Eine weitere Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
},
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Ablehnen",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
}
|
||||
],
|
||||
"type": "SELECT"
|
||||
},
|
||||
{
|
||||
"title": "Eine weitere Zustimmung erforderlich",
|
||||
"description": "Bitte wählen Sie eine Option aus, um fortzufahren.",
|
||||
"options": [
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Zustimmen",
|
||||
"processingPurpose": "BUSINESS_PROCESS",
|
||||
"employeeDataCategory": "SENSITIVE"
|
||||
},
|
||||
{
|
||||
"value": "false",
|
||||
"label": "Ablehnen",
|
||||
"processingPurpose": "DATA_ANALYSIS",
|
||||
"employeeDataCategory": "REVIEW_REQUIRED"
|
||||
}
|
||||
],
|
||||
"type": "RADIOBUTTON"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user