feat(#38): Add periodic local storage backup for application form
This commit is contained in:
@@ -43,9 +43,45 @@
|
||||
</FormStepperWithNavigation>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
|
||||
<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>
|
||||
<p
|
||||
v-if="backupSectionIndex !== null && backupSectionIndex !== sectionIndex"
|
||||
class="text-sm text-(--ui-text-muted) mt-2"
|
||||
>
|
||||
{{
|
||||
$t('applicationForms.recovery.sectionNote', {
|
||||
section: (backupSectionIndex + 1).toString()
|
||||
})
|
||||
}}
|
||||
</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 ApplicationFormDto,
|
||||
@@ -54,6 +90,7 @@ import {
|
||||
} from '~~/.api-client'
|
||||
import type { FormElementId } from '~~/types/formElement'
|
||||
import { useApplicationFormValidator } from '~/composables/useApplicationFormValidator'
|
||||
import { useLocalStorageBackup } from '~/composables/useLocalStorageBackup'
|
||||
import { useUserStore } from '~~/stores/useUserStore'
|
||||
import { useCommentStore } from '~~/stores/useCommentStore'
|
||||
|
||||
@@ -91,6 +128,13 @@ const sectionIndex = computed(() => {
|
||||
return !isNaN(index) ? index : 0
|
||||
})
|
||||
|
||||
// LocalStorage backup for auto-save
|
||||
const { hasBackup, backupTimestamp, backupSectionIndex, saveBackup, loadBackup, clearBackup } = useLocalStorageBackup(
|
||||
applicationFormId!
|
||||
)
|
||||
|
||||
const showRecoveryModal = ref(false)
|
||||
|
||||
const isReadOnly = computed(() => {
|
||||
return applicationForm.value?.createdBy?.keycloakId !== user.value?.keycloakId
|
||||
})
|
||||
@@ -102,6 +146,11 @@ onMounted(async () => {
|
||||
await nextTick()
|
||||
originalFormJson.value = JSON.stringify(applicationForm.value)
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
// Show recovery modal if backup exists
|
||||
if (hasBackup.value) {
|
||||
showRecoveryModal.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -165,12 +214,50 @@ watch(sectionIndex, async () => {
|
||||
originalFormJson.value = JSON.stringify(applicationForm.value)
|
||||
})
|
||||
|
||||
// Auto-save to localStorage with 5 second debounce
|
||||
watchDebounced(
|
||||
() => applicationForm.value,
|
||||
(form) => {
|
||||
if (form && hasUnsavedChanges.value && !isReadOnly.value) {
|
||||
saveBackup(form, sectionIndex.value)
|
||||
}
|
||||
},
|
||||
{ debounce: 5000, deep: true }
|
||||
)
|
||||
|
||||
function handleRestoreBackup() {
|
||||
const backupData = loadBackup()
|
||||
if (backupData && applicationForm.value) {
|
||||
updateApplicationForm(backupData)
|
||||
originalFormJson.value = JSON.stringify(backupData)
|
||||
showRecoveryModal.value = false
|
||||
clearBackup()
|
||||
|
||||
// Navigate to the section user was editing
|
||||
if (backupSectionIndex.value !== null && backupSectionIndex.value !== sectionIndex.value) {
|
||||
navigateTo(`/application-forms/${applicationFormId}/${backupSectionIndex.value}`)
|
||||
}
|
||||
|
||||
toast.add({
|
||||
title: $t('common.success'),
|
||||
description: $t('applicationForms.recovery.restore'),
|
||||
color: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleDiscardBackup() {
|
||||
clearBackup()
|
||||
showRecoveryModal.value = false
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
if (applicationForm.value?.id) {
|
||||
const updated = await updateForm(applicationForm.value.id, applicationForm.value)
|
||||
if (updated) {
|
||||
updateApplicationForm(updated)
|
||||
originalFormJson.value = JSON.stringify(updated)
|
||||
clearBackup()
|
||||
toast.add({ title: $t('common.success'), description: $t('applicationForms.saved'), color: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user