feat(fullstack): Add application form status, add submissions of forms, update DB schema
This commit is contained in:
@@ -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 {
|
||||
createApplicationForm,
|
||||
getAllApplicationForms,
|
||||
getApplicationFormById,
|
||||
updateApplicationForm,
|
||||
deleteApplicationFormById
|
||||
deleteApplicationFormById,
|
||||
submitApplicationForm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +45,16 @@ export function useApplicationFormApi() {
|
||||
return applicationFormApiClient.deleteApplicationForm({ id })
|
||||
}
|
||||
|
||||
async function submitApplicationForm(id: string): Promise<ApplicationFormDto> {
|
||||
return applicationFormApiClient.submitApplicationForm({ id })
|
||||
}
|
||||
|
||||
return {
|
||||
createApplicationForm,
|
||||
getAllApplicationForms,
|
||||
getApplicationFormById,
|
||||
updateApplicationForm,
|
||||
deleteApplicationFormById
|
||||
deleteApplicationFormById,
|
||||
submitApplicationForm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,15 @@
|
||||
|
||||
<UDashboardToolbar>
|
||||
<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>
|
||||
</UDashboardToolbar>
|
||||
</template>
|
||||
@@ -51,14 +59,15 @@
|
||||
>
|
||||
Next
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="!stepper?.hasNext"
|
||||
trailing-icon="i-lucide-send-horizontal"
|
||||
:disabled="isReadOnly"
|
||||
@click="onSubmit"
|
||||
>
|
||||
Submit
|
||||
</UButton>
|
||||
|
||||
<div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5">
|
||||
<UButton trailing-icon="i-lucide-save" :disabled="isReadOnly" variant="outline" @click="onSave">
|
||||
Save
|
||||
</UButton>
|
||||
<UButton trailing-icon="i-lucide-send-horizontal" :disabled="isReadOnly" @click="onSubmit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
@@ -70,13 +79,14 @@
|
||||
import type { ApplicationFormDto, FormElementSectionDto } from '~/.api-client'
|
||||
import type { StepperItem } from '@nuxt/ui'
|
||||
|
||||
const { getApplicationFormById, updateApplicationForm } = useApplicationForm()
|
||||
const { getApplicationFormById, updateApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||
const route = useRoute()
|
||||
const { user } = useAuth()
|
||||
const toast = useToast()
|
||||
|
||||
definePageMeta({
|
||||
// Prevent whole page from re-rendering when navigating between sections to keep state
|
||||
key: (route) => `${route.params.id}`,
|
||||
key: (route) => `${route.params.id}`
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -136,10 +146,18 @@ async function navigateStepper(direction: 'forward' | 'backward') {
|
||||
await navigateTo(`/application-forms/${route.params.id}/${activeStepperItemIndex.value}`)
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
async function onSave() {
|
||||
if (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('/')
|
||||
toast.add({ title: 'Success', description: 'Application form submitted', color: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
|
||||
<template #body>
|
||||
<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">
|
||||
<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>
|
||||
<p class="text-gray-500 mb-4">
|
||||
Sie haben keine Berechtigung zum Erstellen von Anträgen.
|
||||
</p>
|
||||
<UAlert
|
||||
<p class="text-gray-500 mb-4">Sie haben keine Berechtigung zum Erstellen von Anträgen.</p>
|
||||
<UAlert
|
||||
v-if="currentRoleInfo"
|
||||
:title="`Ihre aktuelle Rolle: ${currentRoleInfo.name}`"
|
||||
:description="currentRoleInfo.description"
|
||||
@@ -32,59 +29,48 @@
|
||||
class="max-w-md mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{{ ampelStatusEmoji }}
|
||||
{{ trafficLightStatusEmoji }}
|
||||
</UPageCard>
|
||||
|
||||
<UPageCard variant="subtle">
|
||||
<UForm class="space-y-4" :state="{}" @submit="onSubmit">
|
||||
<UFormField label="Name">
|
||||
<UInput v-if="applicationFormTemplate" v-model="applicationFormTemplate.name" />
|
||||
</UFormField>
|
||||
<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">
|
||||
{{ 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>
|
||||
<UPageCard variant="subtle">
|
||||
<UForm class="space-y-4" :state="{}" @submit="onSubmit">
|
||||
<UFormField label="Name">
|
||||
<UInput v-if="applicationFormTemplate" v-model="applicationFormTemplate.name" />
|
||||
</UFormField>
|
||||
<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">
|
||||
{{ 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>
|
||||
<UButton
|
||||
v-if="stepper?.hasNext"
|
||||
trailing-icon="i-lucide-arrow-right"
|
||||
:disabled="!stepper?.hasNext"
|
||||
@click="navigateStepper('forward')"
|
||||
>
|
||||
Next
|
||||
</UButton>
|
||||
<div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5">
|
||||
<UButton trailing-icon="i-lucide-save" variant="outline" @click="onSave"> Save </UButton>
|
||||
<UButton trailing-icon="i-lucide-send-horizontal" @click="onSubmit"> Submit </UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UForm>
|
||||
</UPageCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -98,10 +84,11 @@ import type { FormElementId } from '~/types/FormElement'
|
||||
import type { StepperItem } from '@nuxt/ui'
|
||||
|
||||
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
||||
const { createApplicationForm } = useApplicationForm()
|
||||
const { createApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { userDto, selectedOrganization } = useAuth()
|
||||
const { canCreateApplicationForm, getCurrentRoleInfo } = usePermissions()
|
||||
const toast = useToast()
|
||||
|
||||
// Get current role information for display
|
||||
const currentRoleInfo = computed(() => getCurrentRoleInfo())
|
||||
@@ -174,7 +161,7 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const ampelStatusEmoji = computed(() => {
|
||||
const trafficLightStatusEmoji = computed(() => {
|
||||
switch (validationStatus.value) {
|
||||
case ComplianceStatus.Critical:
|
||||
return '🔴'
|
||||
@@ -187,16 +174,32 @@ const ampelStatusEmoji = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
if (applicationFormTemplate.value) {
|
||||
applicationFormTemplate.value.createdBy = userDto.value
|
||||
applicationFormTemplate.value.lastModifiedBy = userDto.value
|
||||
applicationFormTemplate.value.organizationId = selectedOrganization.value?.id ?? ''
|
||||
|
||||
await createApplicationForm(applicationFormTemplate.value)
|
||||
await navigateTo('/')
|
||||
} else {
|
||||
console.error('Application form data is undefined')
|
||||
async function onSave() {
|
||||
const applicationForm = await prepareAndCreateApplicationForm()
|
||||
if (applicationForm) {
|
||||
toast.add({ title: 'Success', description: 'Application form saved', color: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
<p class="text-(--ui-text-muted) text-sm">
|
||||
Erstellt von {{ applicationFormElem.createdBy.name }} am {{ formatDate(applicationFormElem.createdAt) }}
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<UChip size="sm">
|
||||
{{ applicationFormElem.status }}
|
||||
</UChip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<UPageLinks :links="getLinksForApplicationForm(applicationFormElem)" />
|
||||
|
||||
Reference in New Issue
Block a user