From cb9abeed7fae518aa0ae1295cf1da0c38f53f4b2 Mon Sep 17 00:00:00 2001 From: Denis Lugowski Date: Sun, 1 Jun 2025 18:16:38 +0200 Subject: [PATCH] feat(fullstack): Add form element section and stepper --- .../api/legalconsenthub.yml | 64 ++++++- .../application_form/ApplicationForm.kt | 4 +- .../application_form/ApplicationFormMapper.kt | 12 +- .../FormElementSectionNotFoundException.kt | 5 + .../form_element/FormElement.kt | 14 +- .../form_element/FormElementMapper.kt | 20 +-- .../form_element/FormElementSection.kt | 36 ++++ .../form_element/FormElementSectionMapper.kt | 53 ++++++ .../FormElementSectionRepository.kt | 8 + legalconsenthub/components/FormEngine.vue | 2 +- .../pages/application-forms/[id].vue | 82 --------- .../application-forms/[id]/[sectionIndex].vue | 159 ++++++++++++++++++ legalconsenthub/pages/create.vue | 78 ++++++++- legalconsenthub/pages/index.vue | 2 +- testdata.json | 154 +++++++++-------- 15 files changed, 497 insertions(+), 196 deletions(-) create mode 100644 legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/FormElementSectionNotFoundException.kt create mode 100644 legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt create mode 100644 legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt create mode 100644 legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionRepository.kt delete mode 100644 legalconsenthub/pages/application-forms/[id].vue create mode 100644 legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue diff --git a/legalconsenthub-backend/api/legalconsenthub.yml b/legalconsenthub-backend/api/legalconsenthub.yml index dfce7d7..8a3c20d 100644 --- a/legalconsenthub-backend/api/legalconsenthub.yml +++ b/legalconsenthub-backend/api/legalconsenthub.yml @@ -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 diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationForm.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationForm.kt index df86c27..bd68e5d 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationForm.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationForm.kt @@ -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 = mutableListOf(), + var formElementSections: MutableList = mutableListOf(), @Column(nullable = false) var isTemplate: Boolean, diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt index 7de3179..23bddf1 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/application_form/ApplicationFormMapper.kt @@ -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 } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/FormElementSectionNotFoundException.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/FormElementSectionNotFoundException.kt new file mode 100644 index 0000000..51a4ec7 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/error/FormElementSectionNotFoundException.kt @@ -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") diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt index 4164067..3876b9f 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElement.kt @@ -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 = mutableListOf(), @Column(nullable = false) - var type: FormElementType + var type: FormElementType, + + @ManyToOne + @JoinColumn(name = "form_element_section_id", nullable = false) + var formElementSection: FormElementSection? = null ) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt index 1891a4a..1d2280a 100644 --- a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementMapper.kt @@ -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 ) } } diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt new file mode 100644 index 0000000..58bfa84 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSection.kt @@ -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 = mutableListOf(), + + @ManyToOne + @JoinColumn(name = "application_form_id", nullable = false) + var applicationForm: ApplicationForm? = null, +) diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt new file mode 100644 index 0000000..97869b0 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionMapper.kt @@ -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 + } +} diff --git a/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionRepository.kt b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionRepository.kt new file mode 100644 index 0000000..8a6ac08 --- /dev/null +++ b/legalconsenthub-backend/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub/form_element/FormElementSectionRepository.kt @@ -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 diff --git a/legalconsenthub/components/FormEngine.vue b/legalconsenthub/components/FormEngine.vue index e250ba2..b8ecc99 100644 --- a/legalconsenthub/components/FormEngine.vue +++ b/legalconsenthub/components/FormEngine.vue @@ -50,7 +50,7 @@ diff --git a/legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue b/legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue new file mode 100644 index 0000000..1b965d9 --- /dev/null +++ b/legalconsenthub/pages/application-forms/[id]/[sectionIndex].vue @@ -0,0 +1,159 @@ + + + diff --git a/legalconsenthub/pages/create.vue b/legalconsenthub/pages/create.vue index a963917..7e04920 100644 --- a/legalconsenthub/pages/create.vue +++ b/legalconsenthub/pages/create.vue @@ -26,8 +26,33 @@ - - Submit + +

+ {{ currentFormElementSection.title }} +

+ +
+ + Prev + + + + Next + + Submit +
@@ -36,20 +61,60 @@