Files
gremiumhub/legalconsenthub/app/pages/administration.vue

292 lines
8.0 KiB
Vue

<template>
<UDashboardPanel id="administration">
<template #header>
<UDashboardNavbar :title="$t('templates.editorTitle')" :ui="{ right: 'gap-3' }">
<template #leading>
<UDashboardSidebarCollapse />
</template>
<template #right>
<UButton
v-if="hasUnsavedChanges"
icon="i-lucide-rotate-ccw"
:label="$t('templates.reset')"
color="neutral"
variant="outline"
@click="resetEditor"
/>
<UButton
icon="i-lucide-file-plus"
:label="$t('templates.newTemplate')"
color="neutral"
variant="outline"
@click="createNewTemplate"
/>
<UButton
icon="i-lucide-save"
:label="$t('common.save')"
: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">
{{ $t('templates.lastModified') }}
{{ currentTemplate.modifiedAt ? formatDate(new Date(currentTemplate.modifiedAt)) : '-' }}
</p>
</div>
<UBadge v-if="hasUnsavedChanges" :label="$t('templates.unsavedChanges')" 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">{{ $t('templates.noTemplateFound') }}</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="$t('templates.invalidJson')"
:description="$t('templates.invalidJsonDescription')"
/>
</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()
const { t: $t } = useI18n()
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: $t('common.error'),
description: $t('templates.invalidJsonDescription'),
color: 'error'
})
return
}
isSaving.value = true
try {
const parsedData = JSON.parse(editorContent.value)
const dataWithDates = convertDates(parsedData)
if (currentTemplate.value?.id) {
currentTemplate.value = await updateApplicationFormTemplate(
currentTemplate.value.id,
dataWithDates as ApplicationFormDto
)
toast.add({
title: $t('common.success'),
description: $t('templates.updated'),
color: 'success'
})
} else {
currentTemplate.value = await createApplicationFormTemplate(dataWithDates as ApplicationFormDto)
toast.add({
title: $t('common.success'),
description: $t('templates.created'),
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: $t('common.error'),
description: $t('templates.saveError'),
color: 'error'
})
console.error('Error saving template:', error)
} finally {
isSaving.value = false
}
}
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm($t('templates.unsavedWarning'))
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>