feat(fullstack): Add application form status, add submissions of forms, update DB schema
This commit is contained in:
@@ -193,6 +193,37 @@ paths:
|
|||||||
"503":
|
"503":
|
||||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||||
|
|
||||||
|
/application-forms/{id}/submit:
|
||||||
|
post:
|
||||||
|
summary: Submit an application form
|
||||||
|
operationId: submitApplicationForm
|
||||||
|
tags:
|
||||||
|
- application-form
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Application form successfully submitted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ApplicationFormDto"
|
||||||
|
"400":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest"
|
||||||
|
"401":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||||
|
"404":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound"
|
||||||
|
"500":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||||
|
"503":
|
||||||
|
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||||
|
|
||||||
####### Application Form Templates #######
|
####### Application Form Templates #######
|
||||||
/application-form-templates:
|
/application-form-templates:
|
||||||
get:
|
get:
|
||||||
@@ -672,6 +703,7 @@ components:
|
|||||||
- lastModifiedBy
|
- lastModifiedBy
|
||||||
- createdAt
|
- createdAt
|
||||||
- modifiedAt
|
- modifiedAt
|
||||||
|
- status
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
@@ -696,6 +728,8 @@ components:
|
|||||||
modifiedAt:
|
modifiedAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
status:
|
||||||
|
$ref: "#/components/schemas/ApplicationFormStatus"
|
||||||
|
|
||||||
CreateApplicationFormDto:
|
CreateApplicationFormDto:
|
||||||
required:
|
required:
|
||||||
@@ -715,6 +749,9 @@ components:
|
|||||||
default: false
|
default: false
|
||||||
organizationId:
|
organizationId:
|
||||||
type: string
|
type: string
|
||||||
|
status:
|
||||||
|
$ref: "#/components/schemas/ApplicationFormStatus"
|
||||||
|
default: DRAFT
|
||||||
|
|
||||||
PagedApplicationFormDto:
|
PagedApplicationFormDto:
|
||||||
type: object
|
type: object
|
||||||
@@ -1009,6 +1046,15 @@ components:
|
|||||||
- WARNING
|
- WARNING
|
||||||
- CRITICAL
|
- CRITICAL
|
||||||
|
|
||||||
|
ApplicationFormStatus:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- DRAFT
|
||||||
|
- SUBMITTED
|
||||||
|
- APPROVED
|
||||||
|
- REJECTED
|
||||||
|
- SIGNED
|
||||||
|
|
||||||
####### Supporting components #######
|
####### Supporting components #######
|
||||||
Page:
|
Page:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package com.betriebsratkanzlei.legalconsenthub.application_form
|
|||||||
|
|
||||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection
|
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection
|
||||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
|
||||||
import jakarta.persistence.AttributeOverride
|
import jakarta.persistence.AttributeOverride
|
||||||
import jakarta.persistence.AttributeOverrides
|
import jakarta.persistence.AttributeOverrides
|
||||||
import jakarta.persistence.CascadeType
|
import jakarta.persistence.CascadeType
|
||||||
import jakarta.persistence.Column
|
import jakarta.persistence.Column
|
||||||
import jakarta.persistence.Entity
|
import jakarta.persistence.Entity
|
||||||
import jakarta.persistence.EntityListeners
|
import jakarta.persistence.EntityListeners
|
||||||
|
import jakarta.persistence.Enumerated
|
||||||
|
import jakarta.persistence.EnumType
|
||||||
import jakarta.persistence.GeneratedValue
|
import jakarta.persistence.GeneratedValue
|
||||||
import jakarta.persistence.Id
|
import jakarta.persistence.Id
|
||||||
import jakarta.persistence.OneToMany
|
import jakarta.persistence.OneToMany
|
||||||
@@ -36,6 +39,10 @@ class ApplicationForm(
|
|||||||
|
|
||||||
var organizationId: String = "",
|
var organizationId: String = "",
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
var status: ApplicationFormStatus = ApplicationFormStatus.DRAFT,
|
||||||
|
|
||||||
@Embedded
|
@Embedded
|
||||||
@AttributeOverrides(
|
@AttributeOverrides(
|
||||||
AttributeOverride(name = "id", column = Column(name = "created_by_id", nullable = false)),
|
AttributeOverride(name = "id", column = Column(name = "created_by_id", nullable = false)),
|
||||||
|
|||||||
@@ -77,4 +77,12 @@ class ApplicationFormController(
|
|||||||
applicationFormService.deleteApplicationFormByID(id)
|
applicationFormService.deleteApplicationFormByID(id)
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun submitApplicationForm(id: UUID): ResponseEntity<ApplicationFormDto> {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
applicationFormMapper.toApplicationFormDto(
|
||||||
|
applicationFormService.submitApplicationForm(id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class ApplicationFormMapper(private val formElementSectionMapper: FormElementSec
|
|||||||
createdBy = userMapper.toUserDto(applicationForm.createdBy),
|
createdBy = userMapper.toUserDto(applicationForm.createdBy),
|
||||||
lastModifiedBy = userMapper.toUserDto(applicationForm.lastModifiedBy),
|
lastModifiedBy = userMapper.toUserDto(applicationForm.lastModifiedBy),
|
||||||
createdAt = applicationForm.createdAt ?: LocalDateTime.now(),
|
createdAt = applicationForm.createdAt ?: LocalDateTime.now(),
|
||||||
modifiedAt = applicationForm.modifiedAt ?: LocalDateTime.now()
|
modifiedAt = applicationForm.modifiedAt ?: LocalDateTime.now(),
|
||||||
|
status = applicationForm.status
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ class ApplicationFormMapper(private val formElementSectionMapper: FormElementSec
|
|||||||
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSection(it) }.toMutableList(),
|
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSection(it) }.toMutableList(),
|
||||||
isTemplate = applicationForm.isTemplate,
|
isTemplate = applicationForm.isTemplate,
|
||||||
organizationId = applicationForm.organizationId,
|
organizationId = applicationForm.organizationId,
|
||||||
|
status = applicationForm.status,
|
||||||
createdBy = userMapper.toUser(applicationForm.createdBy),
|
createdBy = userMapper.toUser(applicationForm.createdBy),
|
||||||
lastModifiedBy = userMapper.toUser(applicationForm.lastModifiedBy),
|
lastModifiedBy = userMapper.toUser(applicationForm.lastModifiedBy),
|
||||||
createdAt = applicationForm.createdAt,
|
createdAt = applicationForm.createdAt,
|
||||||
@@ -50,6 +52,7 @@ class ApplicationFormMapper(private val formElementSectionMapper: FormElementSec
|
|||||||
name = createApplicationFormDto.name,
|
name = createApplicationFormDto.name,
|
||||||
isTemplate = createApplicationFormDto.isTemplate,
|
isTemplate = createApplicationFormDto.isTemplate,
|
||||||
organizationId = createApplicationFormDto.organizationId ?: "",
|
organizationId = createApplicationFormDto.organizationId ?: "",
|
||||||
|
status = createApplicationFormDto.status ?: com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus.DRAFT,
|
||||||
createdBy = createdBy,
|
createdBy = createdBy,
|
||||||
lastModifiedBy = lastModifiedBy,
|
lastModifiedBy = lastModifiedBy,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.betriebsratkanzlei.legalconsenthub.application_form
|
package com.betriebsratkanzlei.legalconsenthub.application_form
|
||||||
|
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormInvalidStateException
|
||||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotCreatedException
|
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotCreatedException
|
||||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedException
|
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedException
|
||||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
|
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
|
||||||
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedException
|
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedException
|
||||||
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
|
||||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
|
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.PageRequest
|
import org.springframework.data.domain.PageRequest
|
||||||
@@ -59,4 +61,25 @@ class ApplicationFormService(
|
|||||||
throw ApplicationFormNotDeletedException(e)
|
throw ApplicationFormNotDeletedException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun submitApplicationForm(id: UUID): ApplicationForm {
|
||||||
|
val applicationForm = getApplicationFormById(id)
|
||||||
|
|
||||||
|
if (applicationForm.status != ApplicationFormStatus.DRAFT) {
|
||||||
|
throw ApplicationFormInvalidStateException(
|
||||||
|
applicationFormId = id,
|
||||||
|
currentState = applicationForm.status,
|
||||||
|
expectedState = ApplicationFormStatus.DRAFT,
|
||||||
|
operation = "submit"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationForm.status = ApplicationFormStatus.SUBMITTED
|
||||||
|
|
||||||
|
return try {
|
||||||
|
applicationFormRepository.save(applicationForm)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw ApplicationFormNotUpdatedException(e, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.betriebsratkanzlei.legalconsenthub.error
|
||||||
|
|
||||||
|
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class ApplicationFormInvalidStateException(
|
||||||
|
val applicationFormId: UUID,
|
||||||
|
val currentState: ApplicationFormStatus,
|
||||||
|
val expectedState: ApplicationFormStatus,
|
||||||
|
val operation: String
|
||||||
|
) : RuntimeException("Cannot $operation application form with ID $applicationFormId. Current state: $currentState, expected state: $expectedState")
|
||||||
@@ -31,6 +31,22 @@ class ExceptionHandler {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResponseBody
|
||||||
|
@ExceptionHandler(ApplicationFormInvalidStateException::class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
fun handleInvalidStateError(e: ApplicationFormInvalidStateException): ResponseEntity<ProblemDetails> {
|
||||||
|
logger.warn(e.message, e)
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_PROBLEM_JSON)
|
||||||
|
.body(
|
||||||
|
ProblemDetails(
|
||||||
|
title = "Invalid State",
|
||||||
|
status = HttpStatus.BAD_REQUEST.value(),
|
||||||
|
type = URI.create("about:blank"),
|
||||||
|
detail = e.message ?: "Operation not allowed in current state"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@ExceptionHandler(
|
@ExceptionHandler(
|
||||||
ApplicationFormNotCreatedException::class,
|
ApplicationFormNotCreatedException::class,
|
||||||
|
|||||||
@@ -10,18 +10,20 @@ create table application_form
|
|||||||
last_modified_by_name varchar(255) not null,
|
last_modified_by_name varchar(255) not null,
|
||||||
name varchar(255) not null,
|
name varchar(255) not null,
|
||||||
organization_id varchar(255),
|
organization_id varchar(255),
|
||||||
|
status enum ('APPROVED','DRAFT','REJECTED','SIGNED','SUBMITTED') not null,
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table comment
|
create table comment
|
||||||
(
|
(
|
||||||
created_at timestamp(6) not null,
|
created_at timestamp(6) not null,
|
||||||
modified_at timestamp(6) not null,
|
modified_at timestamp(6) not null,
|
||||||
form_element_id uuid not null,
|
application_form_id uuid not null,
|
||||||
id uuid not null,
|
form_element_id uuid not null,
|
||||||
created_by_id varchar(255) not null,
|
id uuid not null,
|
||||||
created_by_name varchar(255) not null,
|
created_by_id varchar(255) not null,
|
||||||
message varchar(255) not null,
|
created_by_name varchar(255) not null,
|
||||||
|
message varchar(255) not null,
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -36,12 +38,29 @@ create table form_element_options
|
|||||||
|
|
||||||
create table form_element
|
create table form_element
|
||||||
(
|
(
|
||||||
type tinyint not null check (type between 0 and 4),
|
type tinyint not null check (type between 0 and 4),
|
||||||
application_form_id uuid not null,
|
form_element_section_id uuid not null,
|
||||||
id uuid not null,
|
id uuid not null,
|
||||||
|
description varchar(255),
|
||||||
|
title varchar(255),
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table form_element_section
|
||||||
|
(
|
||||||
|
application_form_id uuid not null,
|
||||||
|
id uuid not null,
|
||||||
|
description varchar(255),
|
||||||
|
short_title varchar(255),
|
||||||
|
title varchar(255) not null,
|
||||||
|
primary key (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
alter table if exists comment
|
||||||
|
add constraint FKlavy9axrt26sepreg5lqtuoap
|
||||||
|
foreign key (application_form_id)
|
||||||
|
references application_form;
|
||||||
|
|
||||||
alter table if exists comment
|
alter table if exists comment
|
||||||
add constraint FKfg84w0i76tw9os13950272c6f
|
add constraint FKfg84w0i76tw9os13950272c6f
|
||||||
foreign key (form_element_id)
|
foreign key (form_element_id)
|
||||||
@@ -53,6 +72,11 @@ alter table if exists form_element_options
|
|||||||
references form_element;
|
references form_element;
|
||||||
|
|
||||||
alter table if exists form_element
|
alter table if exists form_element
|
||||||
add constraint FKdniyq3l10lncw48tft15js5gb
|
add constraint FKdpr6k93m4hqllqjsvoa4or6mp
|
||||||
|
foreign key (form_element_section_id)
|
||||||
|
references form_element_section;
|
||||||
|
|
||||||
|
alter table if exists form_element_section
|
||||||
|
add constraint FKtn0lreovauwf2v29doo70o3qs
|
||||||
foreign key (application_form_id)
|
foreign key (application_form_id)
|
||||||
references application_form;
|
references application_form;
|
||||||
|
|||||||
@@ -83,11 +83,29 @@ export function useApplicationForm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitApplicationForm(id: string): Promise<ApplicationFormDto> {
|
||||||
|
if (!id) {
|
||||||
|
return Promise.reject(new Error('ID missing'))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await applicationFormApi.submitApplicationForm(id)
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (e instanceof ResponseError) {
|
||||||
|
console.error(`Failed submitting application form with ID ${id}:`, e.response)
|
||||||
|
} else {
|
||||||
|
console.error(`Failed submitting application form with ID ${id}:`, e)
|
||||||
|
}
|
||||||
|
return Promise.reject(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createApplicationForm,
|
createApplicationForm,
|
||||||
getAllApplicationForms,
|
getAllApplicationForms,
|
||||||
getApplicationFormById,
|
getApplicationFormById,
|
||||||
updateApplicationForm,
|
updateApplicationForm,
|
||||||
deleteApplicationFormById
|
deleteApplicationFormById,
|
||||||
|
submitApplicationForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,16 @@ export function useApplicationFormApi() {
|
|||||||
return applicationFormApiClient.deleteApplicationForm({ id })
|
return applicationFormApiClient.deleteApplicationForm({ id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitApplicationForm(id: string): Promise<ApplicationFormDto> {
|
||||||
|
return applicationFormApiClient.submitApplicationForm({ id })
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createApplicationForm,
|
createApplicationForm,
|
||||||
getAllApplicationForms,
|
getAllApplicationForms,
|
||||||
getApplicationFormById,
|
getApplicationFormById,
|
||||||
updateApplicationForm,
|
updateApplicationForm,
|
||||||
deleteApplicationFormById
|
deleteApplicationFormById,
|
||||||
|
submitApplicationForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,15 @@
|
|||||||
|
|
||||||
<UDashboardToolbar>
|
<UDashboardToolbar>
|
||||||
<template #right>
|
<template #right>
|
||||||
<UButton icon="i-lucide-file-text" size="md" color="primary" variant="solid" target="_blank" :to="`/api/application-forms/${applicationForm.id}/pdf`">PDF Vorschau</UButton>
|
<UButton
|
||||||
|
icon="i-lucide-file-text"
|
||||||
|
size="md"
|
||||||
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
target="_blank"
|
||||||
|
:to="`/api/application-forms/${applicationForm.id}/pdf`"
|
||||||
|
>PDF Vorschau</UButton
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardToolbar>
|
</UDashboardToolbar>
|
||||||
</template>
|
</template>
|
||||||
@@ -51,14 +59,15 @@
|
|||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
|
||||||
v-if="!stepper?.hasNext"
|
<div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5">
|
||||||
trailing-icon="i-lucide-send-horizontal"
|
<UButton trailing-icon="i-lucide-save" :disabled="isReadOnly" variant="outline" @click="onSave">
|
||||||
:disabled="isReadOnly"
|
Save
|
||||||
@click="onSubmit"
|
</UButton>
|
||||||
>
|
<UButton trailing-icon="i-lucide-send-horizontal" :disabled="isReadOnly" @click="onSubmit">
|
||||||
Submit
|
Submit
|
||||||
</UButton>
|
</UButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UCard>
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,13 +79,14 @@
|
|||||||
import type { ApplicationFormDto, FormElementSectionDto } from '~/.api-client'
|
import type { ApplicationFormDto, FormElementSectionDto } from '~/.api-client'
|
||||||
import type { StepperItem } from '@nuxt/ui'
|
import type { StepperItem } from '@nuxt/ui'
|
||||||
|
|
||||||
const { getApplicationFormById, updateApplicationForm } = useApplicationForm()
|
const { getApplicationFormById, updateApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
// Prevent whole page from re-rendering when navigating between sections to keep state
|
// Prevent whole page from re-rendering when navigating between sections to keep state
|
||||||
key: (route) => `${route.params.id}`,
|
key: (route) => `${route.params.id}`
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -136,10 +146,18 @@ async function navigateStepper(direction: 'forward' | 'backward') {
|
|||||||
await navigateTo(`/application-forms/${route.params.id}/${activeStepperItemIndex.value}`)
|
await navigateTo(`/application-forms/${route.params.id}/${activeStepperItemIndex.value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSave() {
|
||||||
if (data?.value) {
|
if (data?.value) {
|
||||||
await updateApplicationForm(data.value.id, data.value)
|
await updateApplicationForm(data.value.id, data.value)
|
||||||
|
toast.add({ title: 'Success', description: 'Application form saved', color: 'success' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
if (data?.value) {
|
||||||
|
await submitApplicationForm(data.value.id)
|
||||||
await navigateTo('/')
|
await navigateTo('/')
|
||||||
|
toast.add({ title: 'Success', description: 'Application form submitted', color: 'success' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,11 @@
|
|||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-4xl mx-auto">
|
<div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-4xl mx-auto">
|
||||||
<!-- Permission Guard using Better Auth's native system -->
|
|
||||||
<div v-if="!canCreateApplicationForm" class="text-center py-12">
|
<div v-if="!canCreateApplicationForm" class="text-center py-12">
|
||||||
<UIcon name="i-lucide-shield-x" class="w-16 h-16 mx-auto text-red-400 mb-4" />
|
<UIcon name="i-lucide-shield-x" class="w-16 h-16 mx-auto text-red-400 mb-4" />
|
||||||
<h2 class="text-2xl font-semibold text-gray-700 mb-2">Keine Berechtigung</h2>
|
<h2 class="text-2xl font-semibold text-gray-700 mb-2">Keine Berechtigung</h2>
|
||||||
<p class="text-gray-500 mb-4">
|
<p class="text-gray-500 mb-4">Sie haben keine Berechtigung zum Erstellen von Anträgen.</p>
|
||||||
Sie haben keine Berechtigung zum Erstellen von Anträgen.
|
<UAlert
|
||||||
</p>
|
|
||||||
<UAlert
|
|
||||||
v-if="currentRoleInfo"
|
v-if="currentRoleInfo"
|
||||||
:title="`Ihre aktuelle Rolle: ${currentRoleInfo.name}`"
|
:title="`Ihre aktuelle Rolle: ${currentRoleInfo.name}`"
|
||||||
:description="currentRoleInfo.description"
|
:description="currentRoleInfo.description"
|
||||||
@@ -32,59 +29,48 @@
|
|||||||
class="max-w-md mx-auto"
|
class="max-w-md mx-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
Erstelle Formular für Organisation: {{ selectedOrganization?.name }}
|
|
||||||
|
|
||||||
<!-- Role Context Alert -->
|
|
||||||
<UAlert
|
|
||||||
v-if="currentRoleInfo"
|
|
||||||
:title="`Erstellen als: ${currentRoleInfo.name}`"
|
|
||||||
:description="`${currentRoleInfo.description} - Sie können Anträge erstellen und bearbeiten.`"
|
|
||||||
:color="currentRoleInfo.color"
|
|
||||||
variant="soft"
|
|
||||||
:icon="currentRoleInfo.icon"
|
|
||||||
class="mb-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UPageCard title="Ampelstatus" variant="naked" orientation="horizontal" class="mb-4">
|
<UPageCard title="Ampelstatus" variant="naked" orientation="horizontal" class="mb-4">
|
||||||
{{ ampelStatusEmoji }}
|
{{ trafficLightStatusEmoji }}
|
||||||
</UPageCard>
|
</UPageCard>
|
||||||
|
|
||||||
<UPageCard variant="subtle">
|
<UPageCard variant="subtle">
|
||||||
<UForm class="space-y-4" :state="{}" @submit="onSubmit">
|
<UForm class="space-y-4" :state="{}" @submit="onSubmit">
|
||||||
<UFormField label="Name">
|
<UFormField label="Name">
|
||||||
<UInput v-if="applicationFormTemplate" v-model="applicationFormTemplate.name" />
|
<UInput v-if="applicationFormTemplate" v-model="applicationFormTemplate.name" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
<UStepper ref="stepper" v-model="activeStepperItemIndex" :items="stepperItems" class="w-full" />
|
<UStepper ref="stepper" v-model="activeStepperItemIndex" :items="stepperItems" class="w-full" />
|
||||||
<h1 v-if="currentFormElementSection?.title" class="text-xl text-pretty font-bold text-highlighted">
|
<h1 v-if="currentFormElementSection?.title" class="text-xl text-pretty font-bold text-highlighted">
|
||||||
{{ currentFormElementSection.title }}
|
{{ currentFormElementSection.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<FormEngine
|
<FormEngine
|
||||||
v-if="currentFormElementSection?.formElements"
|
v-if="currentFormElementSection?.formElements"
|
||||||
v-model="currentFormElementSection.formElements"
|
v-model="currentFormElementSection.formElements"
|
||||||
/>
|
/>
|
||||||
<div class="flex gap-2 justify-between mt-4">
|
<div class="flex gap-2 justify-between mt-4">
|
||||||
<UButton
|
<UButton
|
||||||
leading-icon="i-lucide-arrow-left"
|
leading-icon="i-lucide-arrow-left"
|
||||||
:disabled="!stepper?.hasPrev"
|
:disabled="!stepper?.hasPrev"
|
||||||
@click="navigateStepper('backward')"
|
@click="navigateStepper('backward')"
|
||||||
>
|
>
|
||||||
Prev
|
Prev
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
v-if="stepper?.hasNext"
|
v-if="stepper?.hasNext"
|
||||||
trailing-icon="i-lucide-arrow-right"
|
trailing-icon="i-lucide-arrow-right"
|
||||||
:disabled="!stepper?.hasNext"
|
:disabled="!stepper?.hasNext"
|
||||||
@click="navigateStepper('forward')"
|
@click="navigateStepper('forward')"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton v-if="!stepper?.hasNext" @click="onSubmit"> Submit </UButton>
|
<div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5">
|
||||||
</div>
|
<UButton trailing-icon="i-lucide-save" variant="outline" @click="onSave"> Save </UButton>
|
||||||
</UForm>
|
<UButton trailing-icon="i-lucide-send-horizontal" @click="onSubmit"> Submit </UButton>
|
||||||
</UPageCard>
|
</div>
|
||||||
|
</div>
|
||||||
|
</UForm>
|
||||||
|
</UPageCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -98,10 +84,11 @@ import type { FormElementId } from '~/types/FormElement'
|
|||||||
import type { StepperItem } from '@nuxt/ui'
|
import type { StepperItem } from '@nuxt/ui'
|
||||||
|
|
||||||
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
||||||
const { createApplicationForm } = useApplicationForm()
|
const { createApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||||
const { userDto, selectedOrganization } = useAuth()
|
const { userDto, selectedOrganization } = useAuth()
|
||||||
const { canCreateApplicationForm, getCurrentRoleInfo } = usePermissions()
|
const { canCreateApplicationForm, getCurrentRoleInfo } = usePermissions()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
// Get current role information for display
|
// Get current role information for display
|
||||||
const currentRoleInfo = computed(() => getCurrentRoleInfo())
|
const currentRoleInfo = computed(() => getCurrentRoleInfo())
|
||||||
@@ -174,7 +161,7 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const ampelStatusEmoji = computed(() => {
|
const trafficLightStatusEmoji = computed(() => {
|
||||||
switch (validationStatus.value) {
|
switch (validationStatus.value) {
|
||||||
case ComplianceStatus.Critical:
|
case ComplianceStatus.Critical:
|
||||||
return '🔴'
|
return '🔴'
|
||||||
@@ -187,16 +174,32 @@ const ampelStatusEmoji = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSave() {
|
||||||
if (applicationFormTemplate.value) {
|
const applicationForm = await prepareAndCreateApplicationForm()
|
||||||
applicationFormTemplate.value.createdBy = userDto.value
|
if (applicationForm) {
|
||||||
applicationFormTemplate.value.lastModifiedBy = userDto.value
|
toast.add({ title: 'Success', description: 'Application form saved', color: 'success' })
|
||||||
applicationFormTemplate.value.organizationId = selectedOrganization.value?.id ?? ''
|
|
||||||
|
|
||||||
await createApplicationForm(applicationFormTemplate.value)
|
|
||||||
await navigateTo('/')
|
|
||||||
} else {
|
|
||||||
console.error('Application form data is undefined')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
const applicationForm = await prepareAndCreateApplicationForm()
|
||||||
|
if (applicationForm) {
|
||||||
|
await submitApplicationForm(applicationForm.id)
|
||||||
|
await navigateTo('/')
|
||||||
|
toast.add({ title: 'Success', description: 'Application form submitted', color: 'success' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function prepareAndCreateApplicationForm() {
|
||||||
|
if (!applicationFormTemplate.value) {
|
||||||
|
console.error('Application form data is undefined')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationFormTemplate.value.createdBy = userDto.value
|
||||||
|
applicationFormTemplate.value.lastModifiedBy = userDto.value
|
||||||
|
applicationFormTemplate.value.organizationId = selectedOrganization.value?.id ?? ''
|
||||||
|
|
||||||
|
return await createApplicationForm(applicationFormTemplate.value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -49,6 +49,11 @@
|
|||||||
<p class="text-(--ui-text-muted) text-sm">
|
<p class="text-(--ui-text-muted) text-sm">
|
||||||
Erstellt von {{ applicationFormElem.createdBy.name }} am {{ formatDate(applicationFormElem.createdAt) }}
|
Erstellt von {{ applicationFormElem.createdBy.name }} am {{ formatDate(applicationFormElem.createdAt) }}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<UChip size="sm">
|
||||||
|
{{ applicationFormElem.status }}
|
||||||
|
</UChip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<UPageLinks :links="getLinksForApplicationForm(applicationFormElem)" />
|
<UPageLinks :links="getLinksForApplicationForm(applicationFormElem)" />
|
||||||
|
|||||||
Reference in New Issue
Block a user