feat(#8): Add administration.vue page for adding application form templates
This commit is contained in:
286
legalconsenthub/app/pages/administration.vue
Normal file
286
legalconsenthub/app/pages/administration.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<UDashboardPanel id="administration">
|
||||
<template #header>
|
||||
<UDashboardNavbar title="Administration - JSON Template Editor" :ui="{ right: 'gap-3' }">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="hasUnsavedChanges"
|
||||
icon="i-lucide-rotate-ccw"
|
||||
label="Zurücksetzen"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
@click="resetEditor"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-lucide-file-plus"
|
||||
label="Neue Vorlage"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
@click="createNewTemplate"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-lucide-save"
|
||||
label="Speichern"
|
||||
:disabled="!hasUnsavedChanges || !isValidJson"
|
||||
:loading="isSaving"
|
||||
@click="saveTemplate"
|
||||
/>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4 w-full h-full p-4">
|
||||
<UCard v-if="currentTemplate" class="mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg">{{ currentTemplate.name }}</h3>
|
||||
<p class="text-sm text-muted mt-1">
|
||||
Zuletzt bearbeitet am {{ formatDate(new Date(currentTemplate.modifiedAt)) }}
|
||||
</p>
|
||||
</div>
|
||||
<UBadge v-if="hasUnsavedChanges" label="Ungespeicherte Änderungen" color="warning" variant="subtle" />
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard v-else class="mb-4">
|
||||
<div class="text-center py-4">
|
||||
<UIcon name="i-lucide-info" class="size-8 text-muted mx-auto mb-2" />
|
||||
<p class="text-muted">Keine Vorlage gefunden. Erstellen Sie eine neue Vorlage.</p>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard class="flex-1">
|
||||
<div class="h-[800px] overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<vue-monaco-editor
|
||||
v-model:value="editorContent"
|
||||
language="json"
|
||||
:options="editorOptions"
|
||||
:theme="colorMode.value === 'dark' ? 'vs-dark' : 'vs'"
|
||||
@change="onEditorChange"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UAlert
|
||||
v-if="!isValidJson"
|
||||
color="error"
|
||||
icon="i-lucide-alert-circle"
|
||||
title="Ungültiges JSON"
|
||||
description="Das JSON-Format ist ungültig. Bitte korrigieren Sie die Syntax."
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ApplicationFormDto } from '~~/.api-client'
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
import { formatDate } from '~/utils/date'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default'
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const colorMode = useColorMode()
|
||||
const { hasRole } = usePermissions()
|
||||
const { getAllApplicationFormTemplates, updateApplicationFormTemplate, createApplicationFormTemplate } =
|
||||
await useApplicationFormTemplate()
|
||||
|
||||
if (!hasRole('CHIEF_EXECUTIVE_OFFICER') && !hasRole('IT_DEPARTMENT')) {
|
||||
await navigateTo('/')
|
||||
}
|
||||
|
||||
const currentTemplate = ref<ApplicationFormDto | null>(null)
|
||||
const editorContent = ref<string>('')
|
||||
const originalJson = ref<string>('')
|
||||
const hasUnsavedChanges = ref(false)
|
||||
const isValidJson = ref(true)
|
||||
const isSaving = ref(false)
|
||||
|
||||
const editorOptions = {
|
||||
automaticLayout: true,
|
||||
formatOnPaste: true,
|
||||
formatOnType: true,
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
wrappingStrategy: 'advanced',
|
||||
bracketPairColorization: {
|
||||
enabled: true
|
||||
},
|
||||
guides: {
|
||||
bracketPairs: true,
|
||||
indentation: true
|
||||
},
|
||||
suggest: {
|
||||
showKeywords: true,
|
||||
showSnippets: true
|
||||
}
|
||||
}
|
||||
|
||||
const { data: templatesData } = await useAsyncData('applicationFormTemplates', async () => {
|
||||
return await getAllApplicationFormTemplates()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (templatesData.value?.content && templatesData.value.content.length > 0) {
|
||||
const sortedTemplates = [...templatesData.value.content].sort((a, b) => {
|
||||
const dateA = new Date(a.modifiedAt || 0).getTime()
|
||||
const dateB = new Date(b.modifiedAt || 0).getTime()
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
const latestTemplate = sortedTemplates[0]
|
||||
if (latestTemplate) {
|
||||
currentTemplate.value = latestTemplate
|
||||
editorContent.value = JSON.stringify(currentTemplate.value, null, 2)
|
||||
originalJson.value = editorContent.value
|
||||
}
|
||||
} else {
|
||||
const emptyTemplate = {
|
||||
name: '',
|
||||
description: '',
|
||||
status: 'DRAFT',
|
||||
isTemplate: true,
|
||||
organizationId: '',
|
||||
sections: []
|
||||
}
|
||||
editorContent.value = JSON.stringify(emptyTemplate, null, 2)
|
||||
originalJson.value = editorContent.value
|
||||
}
|
||||
})
|
||||
|
||||
function onEditorChange() {
|
||||
try {
|
||||
JSON.parse(editorContent.value)
|
||||
isValidJson.value = true
|
||||
hasUnsavedChanges.value = editorContent.value !== originalJson.value
|
||||
} catch {
|
||||
isValidJson.value = false
|
||||
hasUnsavedChanges.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function resetEditor() {
|
||||
editorContent.value = originalJson.value
|
||||
hasUnsavedChanges.value = false
|
||||
isValidJson.value = true
|
||||
}
|
||||
|
||||
function createNewTemplate() {
|
||||
currentTemplate.value = null
|
||||
const emptyTemplate = {
|
||||
name: '',
|
||||
description: '',
|
||||
status: 'DRAFT',
|
||||
isTemplate: true,
|
||||
organizationId: '',
|
||||
sections: []
|
||||
}
|
||||
editorContent.value = JSON.stringify(emptyTemplate, null, 2)
|
||||
originalJson.value = editorContent.value
|
||||
hasUnsavedChanges.value = true
|
||||
isValidJson.value = true
|
||||
}
|
||||
|
||||
async function saveTemplate() {
|
||||
if (!isValidJson.value) {
|
||||
toast.add({
|
||||
title: 'Fehler',
|
||||
description: 'Das JSON-Format ist ungültig.',
|
||||
color: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(editorContent.value)
|
||||
const dataWithDates = convertDates(parsedData) as ApplicationFormDto
|
||||
|
||||
if (currentTemplate.value?.id) {
|
||||
currentTemplate.value = await updateApplicationFormTemplate(currentTemplate.value.id, dataWithDates)
|
||||
toast.add({
|
||||
title: 'Erfolg',
|
||||
description: 'Vorlage erfolgreich aktualisiert.',
|
||||
color: 'success'
|
||||
})
|
||||
} else {
|
||||
currentTemplate.value = await createApplicationFormTemplate(dataWithDates)
|
||||
toast.add({
|
||||
title: 'Erfolg',
|
||||
description: 'Vorlage erfolgreich erstellt.',
|
||||
color: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
editorContent.value = JSON.stringify(currentTemplate.value, null, 2)
|
||||
originalJson.value = editorContent.value
|
||||
hasUnsavedChanges.value = false
|
||||
|
||||
await refreshNuxtData('applicationFormTemplates')
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: 'Fehler',
|
||||
description: 'Fehler beim Speichern der Vorlage.',
|
||||
color: 'error'
|
||||
})
|
||||
console.error('Error saving template:', error)
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (hasUnsavedChanges.value) {
|
||||
const answer = window.confirm('Sie haben ungespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?')
|
||||
if (answer) {
|
||||
next()
|
||||
} else {
|
||||
next(false)
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
function convertDates(obj: unknown): unknown {
|
||||
if (obj === null || obj === undefined) return obj
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
const date = new Date(obj)
|
||||
if (!isNaN(date.getTime()) && obj.includes('T') && (obj.includes('Z') || obj.includes('+'))) {
|
||||
return date
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => convertDates(item))
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const converted: Record<string, unknown> = {}
|
||||
for (const key in obj as Record<string, unknown>) {
|
||||
if (key === 'createdAt' || key === 'modifiedAt') {
|
||||
const value = (obj as Record<string, unknown>)[key]
|
||||
converted[key] = value ? new Date(value as string) : value
|
||||
} else {
|
||||
converted[key] = convertDates((obj as Record<string, unknown>)[key])
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user