major: Rename legalconsenthub to gremiumhub
This commit is contained in:
@@ -1,253 +0,0 @@
|
||||
<template>
|
||||
<UDashboardPanel id="home">
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="$t('common.home')" :ui="{ right: 'gap-3' }">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<div class="flex-1" />
|
||||
<USeparator orientation="vertical" class="h-8 mx-2" />
|
||||
<FormValidationIndicator :status="validationStatus" />
|
||||
</UDashboardToolbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div v-if="!canWriteApplicationForms" 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">{{ $t('applicationForms.noPermission') }}</h2>
|
||||
<p class="text-gray-500 mb-4">{{ $t('applicationForms.noPermissionDescription') }}</p>
|
||||
<UButton to="/" class="mt-4"> {{ $t('applicationForms.backToOverview') }} </UButton>
|
||||
</div>
|
||||
<div v-else-if="applicationFormTemplate">
|
||||
<FormStepperWithNavigation
|
||||
:form-element-sections="applicationFormTemplate.formElementSections"
|
||||
@save="onSave"
|
||||
@submit="onSubmit"
|
||||
@add-input-form="handleAddInputForm"
|
||||
@update:form-element-sections="handleFormElementSectionsUpdate"
|
||||
>
|
||||
<UFormField :label="$t('common.name')" class="mb-4">
|
||||
<UInput v-model="applicationFormTemplate.name" class="w-full" />
|
||||
</UFormField>
|
||||
</FormStepperWithNavigation>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
|
||||
<!-- Recovery Modal - outside UDashboardPanel to avoid SSR Teleport hydration conflicts -->
|
||||
<UModal
|
||||
:open="showRecoveryModal"
|
||||
:title="$t('applicationForms.recovery.title')"
|
||||
@update:open="showRecoveryModal = $event"
|
||||
>
|
||||
<template #body>
|
||||
<p class="text-sm text-(--ui-text-muted)">
|
||||
{{
|
||||
$t('applicationForms.recovery.message', {
|
||||
timestamp: backupTimestamp?.toLocaleString()
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UButton color="neutral" variant="outline" @click="handleDiscardBackup">
|
||||
{{ $t('applicationForms.recovery.discard') }}
|
||||
</UButton>
|
||||
<UButton color="primary" @click="handleRestoreBackup">
|
||||
{{ $t('applicationForms.recovery.restore') }}
|
||||
</UButton>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import {
|
||||
ComplianceStatus,
|
||||
type FormElementDto,
|
||||
type FormElementSectionDto,
|
||||
type PagedApplicationFormDto
|
||||
} from '~~/.api-client'
|
||||
import { useApplicationFormValidator } from '~/composables/useApplicationFormValidator'
|
||||
import { useLocalStorageBackup } from '~/composables/useLocalStorageBackup'
|
||||
import type { FormElementId } from '~~/types/formElement'
|
||||
import { useUserStore } from '~~/stores/useUserStore'
|
||||
|
||||
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
||||
const { createApplicationForm, submitApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { evaluateFormElementVisibility } = useFormElementVisibility()
|
||||
const { canWriteApplicationForms } = usePermissions()
|
||||
const userStore = useUserStore()
|
||||
const { selectedOrganization } = storeToRefs(userStore)
|
||||
const toast = useToast()
|
||||
const { t: $t } = useI18n()
|
||||
const logger = useLogger().withTag('create')
|
||||
|
||||
const { data, error } = await useAsyncData<PagedApplicationFormDto>(
|
||||
'create-application-form',
|
||||
async () => {
|
||||
return await getAllApplicationFormTemplates()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
if (error.value) {
|
||||
throw createError({ statusText: error.value.message })
|
||||
}
|
||||
|
||||
const applicationFormTemplate = computed(
|
||||
// TODO: Don't select always the first item, allow user to select a template
|
||||
() => data?.value?.content[0] ?? undefined
|
||||
)
|
||||
|
||||
// LocalStorage backup for auto-save (using 'create' as key for new forms)
|
||||
const { hasBackup, backupTimestamp, saveBackup, loadBackup, clearBackup } = useLocalStorageBackup('create')
|
||||
|
||||
const showRecoveryModal = ref(false)
|
||||
|
||||
const validationMap = ref<Map<FormElementId, ComplianceStatus> | undefined>()
|
||||
const validationStatus = ref<ComplianceStatus>(ComplianceStatus.NonCritical)
|
||||
|
||||
// Unsaved changes tracking
|
||||
const originalFormJson = ref<string>('')
|
||||
|
||||
onMounted(() => {
|
||||
originalFormJson.value = JSON.stringify(applicationFormTemplate.value)
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
// Show recovery modal if backup exists
|
||||
if (hasBackup.value) {
|
||||
showRecoveryModal.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
|
||||
const hasUnsavedChanges = computed(() => {
|
||||
if (!originalFormJson.value) return false
|
||||
return JSON.stringify(applicationFormTemplate.value) !== originalFormJson.value
|
||||
})
|
||||
|
||||
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
||||
if (hasUnsavedChanges.value) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeRouteLeave((_to, _from, next) => {
|
||||
if (hasUnsavedChanges.value) {
|
||||
const answer = window.confirm($t('applicationForms.unsavedWarning'))
|
||||
next(answer)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
const allFormElements = computed(() => {
|
||||
return (
|
||||
applicationFormTemplate.value?.formElementSections?.flatMap((section: FormElementSectionDto) =>
|
||||
section.formElementSubSections.flatMap((subsection) => subsection.formElements)
|
||||
) ?? []
|
||||
)
|
||||
})
|
||||
|
||||
const visibilityMap = computed(() => {
|
||||
return evaluateFormElementVisibility(allFormElements.value)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => allFormElements.value,
|
||||
(updatedFormElements: FormElementDto[]) => {
|
||||
validationMap.value = validateFormElements(updatedFormElements, visibilityMap.value)
|
||||
validationStatus.value = getHighestComplianceStatus()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Auto-save to localStorage with 5 second debounce
|
||||
watchDebounced(
|
||||
() => applicationFormTemplate.value,
|
||||
(form) => {
|
||||
if (form && hasUnsavedChanges.value) {
|
||||
saveBackup(form, 0)
|
||||
}
|
||||
},
|
||||
{ debounce: 5000, deep: true }
|
||||
)
|
||||
|
||||
function handleRestoreBackup() {
|
||||
const backupData = loadBackup()
|
||||
if (backupData && data.value?.content[0]) {
|
||||
// Restore the backed up form data to the template
|
||||
Object.assign(data.value.content[0], backupData)
|
||||
originalFormJson.value = JSON.stringify(backupData)
|
||||
showRecoveryModal.value = false
|
||||
clearBackup()
|
||||
|
||||
toast.add({
|
||||
title: $t('common.success'),
|
||||
description: $t('applicationForms.recovery.restore'),
|
||||
color: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleDiscardBackup() {
|
||||
clearBackup()
|
||||
showRecoveryModal.value = false
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
const applicationForm = await prepareAndCreateApplicationForm()
|
||||
if (applicationForm?.id) {
|
||||
// Reset to prevent unsaved changes warning when navigating
|
||||
originalFormJson.value = JSON.stringify(applicationFormTemplate.value)
|
||||
clearBackup()
|
||||
toast.add({ title: $t('common.success'), description: $t('applicationForms.saved'), color: 'success' })
|
||||
await navigateTo(`/application-forms/${applicationForm.id}/0`)
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const applicationForm = await prepareAndCreateApplicationForm()
|
||||
if (applicationForm?.id) {
|
||||
await submitApplicationForm(applicationForm.id)
|
||||
// Reset to prevent unsaved changes warning when navigating
|
||||
originalFormJson.value = JSON.stringify(applicationFormTemplate.value)
|
||||
clearBackup()
|
||||
await navigateTo('/')
|
||||
toast.add({ title: $t('common.success'), description: $t('applicationForms.submitted'), color: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddInputForm() {
|
||||
// In create mode (no applicationFormId), addInputFormToApplicationForm returns undefined
|
||||
// The form element is added locally to the template sections, which are reactive
|
||||
// No action needed here
|
||||
}
|
||||
|
||||
function handleFormElementSectionsUpdate(sections: FormElementSectionDto[]) {
|
||||
if (applicationFormTemplate.value) {
|
||||
applicationFormTemplate.value.formElementSections = sections
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareAndCreateApplicationForm() {
|
||||
if (!applicationFormTemplate.value) {
|
||||
logger.error('Application form data is undefined')
|
||||
return null
|
||||
}
|
||||
|
||||
logger.debug('selectedOrganization', selectedOrganization.value)
|
||||
applicationFormTemplate.value.organizationId = selectedOrganization.value?.id ?? ''
|
||||
|
||||
return await createApplicationForm(applicationFormTemplate.value)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user