feat(#9): Nuxt 4 migration
This commit is contained in:
25
legalconsenthub/app/components/DeleteModal.vue
Normal file
25
legalconsenthub/app/components/DeleteModal.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<UModal :open="isOpen" title="Mitbestimmungsantrag löschen" @update:open="$emit('update:isOpen', $event)">
|
||||
<template #body>
|
||||
Möchten Sie wirklich den Mitbestimmungsantrag <strong>{{ applicationFormToDelete.name }}</strong> löschen?
|
||||
</template>
|
||||
<template #footer>
|
||||
<UButton label="Abbrechen" color="neutral" variant="outline" @click="$emit('update:isOpen', false)" />
|
||||
<UButton label="Löschen" color="neutral" @click="$emit('delete', applicationFormToDelete.id)" />
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ApplicationFormDto } from '~~/.api-client'
|
||||
|
||||
defineEmits<{
|
||||
(e: 'delete', id: string): void
|
||||
(e: 'update:isOpen', value: boolean): void
|
||||
}>()
|
||||
|
||||
defineProps<{
|
||||
applicationFormToDelete: ApplicationFormDto
|
||||
isOpen: boolean
|
||||
}>()
|
||||
</script>
|
||||
110
legalconsenthub/app/components/FormEngine.vue
Normal file
110
legalconsenthub/app/components/FormEngine.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<template v-for="(formElement, index) in props.modelValue" :key="formElement.id">
|
||||
<div class="flex py-3 lg:py-4">
|
||||
<div class="group flex-auto">
|
||||
<p v-if="formElement.title" class="font-semibold">{{ formElement.title }}</p>
|
||||
<p v-if="formElement.description" class="text-dimmed pb-3">{{ formElement.description }}</p>
|
||||
<component
|
||||
:is="getResolvedComponent(formElement)"
|
||||
:form-options="formElement.options"
|
||||
:disabled="props.disabled"
|
||||
@update:form-options="updateFormOptions($event, index)"
|
||||
/>
|
||||
<TheComment
|
||||
v-if="applicationFormId && activeFormElement === formElement.id"
|
||||
:form-element-id="formElement.id"
|
||||
:application-form-id="applicationFormId"
|
||||
:comments="comments?.[formElement.id]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<UDropdownMenu :items="getDropdownItems(formElement.id, index)" :content="{ align: 'end' }">
|
||||
<UButton icon="i-lucide-ellipsis-vertical" color="neutral" variant="ghost" />
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<USeparator />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormElementDto, FormOptionDto } from '~~/.api-client'
|
||||
import { resolveComponent } from 'vue'
|
||||
import TheComment from '~/components/TheComment.vue'
|
||||
import type { DropdownMenuItem } from '@nuxt/ui'
|
||||
import { useCommentStore } from '~~/stores/useCommentStore'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: FormElementDto[]
|
||||
applicationFormId?: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', formElementDto: FormElementDto[]): void
|
||||
(e: 'click:comments', formElementId: string): void
|
||||
(e: 'add:input-form', position: number): void
|
||||
}>()
|
||||
|
||||
const commentStore = useCommentStore()
|
||||
const { load: loadComments } = commentStore
|
||||
const { comments } = storeToRefs(commentStore)
|
||||
|
||||
if (props.applicationFormId) {
|
||||
console.log('Loading comments for application form:', props.applicationFormId)
|
||||
await loadComments(props.applicationFormId)
|
||||
}
|
||||
|
||||
const activeFormElement = ref('')
|
||||
|
||||
function getResolvedComponent(formElement: FormElementDto) {
|
||||
switch (formElement.type) {
|
||||
case 'CHECKBOX':
|
||||
return resolveComponent('TheCheckbox')
|
||||
case 'SELECT':
|
||||
return resolveComponent('TheSelect')
|
||||
case 'RADIOBUTTON':
|
||||
return resolveComponent('TheRadioGroup')
|
||||
case 'SWITCH':
|
||||
return resolveComponent('TheSwitch')
|
||||
case 'TEXTFIELD':
|
||||
return resolveComponent('TheInput')
|
||||
case 'TITLE_BODY_TEXTFIELDS':
|
||||
return resolveComponent('TheTitleBodyInput')
|
||||
default:
|
||||
return resolveComponent('Unimplemented')
|
||||
}
|
||||
}
|
||||
|
||||
function getDropdownItems(formElementId: string, formElementPosition: number): DropdownMenuItem[] {
|
||||
return [
|
||||
[
|
||||
{
|
||||
label: 'Comments',
|
||||
icon: 'i-lucide-message-square-more',
|
||||
onClick: () => toggleComments(formElementId)
|
||||
},
|
||||
{
|
||||
label: 'Add input field below',
|
||||
icon: 'i-lucide-list-plus',
|
||||
onClick: () => emit('add:input-form', formElementPosition)
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
function updateFormOptions(formOptions: FormOptionDto[], formElementIndex: number) {
|
||||
const updatedModelValue = [...props.modelValue]
|
||||
updatedModelValue[formElementIndex] = { ...updatedModelValue[formElementIndex], options: formOptions }
|
||||
emit('update:modelValue', updatedModelValue)
|
||||
}
|
||||
|
||||
function toggleComments(formElementId: string) {
|
||||
if (activeFormElement.value === formElementId) {
|
||||
activeFormElement.value = ''
|
||||
return
|
||||
}
|
||||
activeFormElement.value = formElementId
|
||||
emit('click:comments', formElementId)
|
||||
}
|
||||
</script>
|
||||
92
legalconsenthub/app/components/NotificationsSlideover.vue
Normal file
92
legalconsenthub/app/components/NotificationsSlideover.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<USlideover v-model:open="isOpen" title="Benachrichtigungen">
|
||||
<template #body>
|
||||
<div v-if="notifications.length === 0" class="text-center py-8 text-muted">
|
||||
<UIcon name="i-heroicons-bell-slash" class="h-8 w-8 mx-auto mb-2" />
|
||||
<p>Keine Benachrichtigungen</p>
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
:to="notification.clickTarget"
|
||||
class="px-3 py-2.5 rounded-md hover:bg-elevated/50 flex items-center gap-3 relative -mx-3 first:-mt-3 last:-mb-3"
|
||||
@click="onNotificationClick(notification)"
|
||||
>
|
||||
<UChip
|
||||
:color="notification.type === 'ERROR' ? 'error' : notification.type === 'WARNING' ? 'warning' : 'primary'"
|
||||
:show="!notification.isRead"
|
||||
inset
|
||||
>
|
||||
<UIcon
|
||||
:name="
|
||||
notification.type === 'ERROR'
|
||||
? 'i-heroicons-x-circle'
|
||||
: notification.type === 'WARNING'
|
||||
? 'i-heroicons-exclamation-triangle'
|
||||
: 'i-heroicons-information-circle'
|
||||
"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</UChip>
|
||||
|
||||
<div class="text-sm flex-1">
|
||||
<p class="flex items-center justify-between">
|
||||
<span class="text-highlighted font-medium">{{ notification.title }}</span>
|
||||
|
||||
<time
|
||||
:datetime="notification.createdAt.toISOString()"
|
||||
class="text-muted text-xs"
|
||||
v-text="formatTimeAgo(notification.createdAt)"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p class="text-dimmed">
|
||||
{{ notification.message }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<UBadge
|
||||
:color="notification.type === 'ERROR' ? 'error' : notification.type === 'WARNING' ? 'warning' : 'info'"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ notification.type }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatTimeAgo } from '@vueuse/core'
|
||||
import type { NotificationDto } from '~~/.api-client'
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
const { notifications, fetchNotifications, handleNotificationClick } = useNotification()
|
||||
|
||||
watch(isOpen, async (newValue) => {
|
||||
if (newValue) {
|
||||
await fetchNotifications()
|
||||
}
|
||||
})
|
||||
|
||||
function onNotificationClick(notification: NotificationDto) {
|
||||
handleNotificationClick(notification)
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
55
legalconsenthub/app/components/ServerConnectionOverlay.vue
Normal file
55
legalconsenthub/app/components/ServerConnectionOverlay.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="!isServerAvailable"
|
||||
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center"
|
||||
@click.prevent
|
||||
@keydown.prevent
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-xl p-8 max-w-md mx-4 text-center">
|
||||
<!-- Loading Spinner -->
|
||||
<div class="mb-6 flex justify-center">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-12 h-12 text-primary-500 animate-spin" />
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{{ t('serverConnection.title') }}
|
||||
</h2>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-6 leading-relaxed">
|
||||
{{ t('serverConnection.message') }}
|
||||
</p>
|
||||
|
||||
<!-- Status Information -->
|
||||
<div class="space-y-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div v-if="isChecking" class="flex items-center justify-center gap-2">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 animate-spin" />
|
||||
<span>{{ t('serverConnection.checking') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="lastCheckTime" class="text-xs">
|
||||
{{ t('serverConnection.lastCheck') }}:
|
||||
{{ new Date(lastCheckTime).toLocaleTimeString() }}
|
||||
</div>
|
||||
|
||||
<div class="text-xs">
|
||||
{{ t('serverConnection.retryInfo') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optional: Manual retry button -->
|
||||
<UButton v-if="!isChecking" variant="ghost" size="sm" class="mt-4" @click="void checkServerHealth()">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-1" />
|
||||
{{ t('serverConnection.retryNow') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { isServerAvailable, isChecking, lastCheckTime, checkServerHealth } = useServerHealth()
|
||||
</script>
|
||||
63
legalconsenthub/app/components/TheComment.vue
Normal file
63
legalconsenthub/app/components/TheComment.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<template v-if="comments && comments.length > 0">
|
||||
<UChatMessages :auto-scroll="false" :should-scroll-to-bottom="false">
|
||||
<UChatMessage
|
||||
v-for="comment in comments"
|
||||
:id="comment.id"
|
||||
:key="comment.id"
|
||||
:avatar="{ icon: 'i-lucide-bot' }"
|
||||
:content="comment.message"
|
||||
role="user"
|
||||
:side="isCommentByUser(comment) ? 'right' : 'left'"
|
||||
variant="subtle"
|
||||
:actions="createChatMessageActions(comment)"
|
||||
>
|
||||
<template #leading="{ avatar }">
|
||||
<div class="flex flex-col">
|
||||
<UAvatar icon="i-lucide-bot" />
|
||||
<p class="text-sm">{{ comment.createdBy.name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</UChatMessage>
|
||||
</UChatMessages>
|
||||
</template>
|
||||
<UTextarea v-model="commentTextAreaValue" class="w-full" />
|
||||
<UButton v-if="!isEditingComment" class="my-3 lg:my-4" @click="submitComment(formElementId)"> Submit </UButton>
|
||||
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="updateEditComment"> Edit comment </UButton>
|
||||
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="cancelEditComment"> Cancel </UButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CommentDto } from '~~/.api-client'
|
||||
import { useCommentTextarea } from '~/composables/comment/useCommentTextarea'
|
||||
|
||||
const props = defineProps<{
|
||||
formElementId: string
|
||||
applicationFormId: string
|
||||
comments?: CommentDto[]
|
||||
}>()
|
||||
|
||||
const commentActions = useCommentTextarea(props.applicationFormId)
|
||||
const {
|
||||
submitComment,
|
||||
updateEditComment,
|
||||
cancelEditComment,
|
||||
editComment,
|
||||
isEditingComment,
|
||||
isCommentByUser,
|
||||
commentTextAreaValue
|
||||
} = commentActions
|
||||
|
||||
function createChatMessageActions(comment: CommentDto) {
|
||||
const chatMessageActions = []
|
||||
|
||||
if (isCommentByUser(comment)) {
|
||||
chatMessageActions.push({
|
||||
label: 'Edit',
|
||||
icon: 'i-lucide-pencil',
|
||||
onClick: () => editComment(comment)
|
||||
})
|
||||
}
|
||||
return chatMessageActions
|
||||
}
|
||||
</script>
|
||||
187
legalconsenthub/app/components/UserMenu.vue
Normal file
187
legalconsenthub/app/components/UserMenu.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<UDropdownMenu
|
||||
:items="items"
|
||||
:content="{ align: 'center', collisionPadding: 12 }"
|
||||
:ui="{ content: collapsed ? 'w-48' : 'w-(--reka-dropdown-menu-trigger-width)' }"
|
||||
>
|
||||
<UButton
|
||||
v-bind="{
|
||||
...user,
|
||||
label: collapsed ? undefined : user?.name,
|
||||
trailingIcon: collapsed ? undefined : 'i-lucide-chevrons-up-down'
|
||||
}"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
block
|
||||
:square="collapsed"
|
||||
class="data-[state=open]:bg-(--ui-bg-elevated)"
|
||||
:ui="{
|
||||
trailingIcon: 'text-(--ui-text-dimmed)'
|
||||
}"
|
||||
/>
|
||||
|
||||
<template #chip-leading="{ item }">
|
||||
<span
|
||||
:style="{ '--chip': `var(--color-${(item as any).chip}-400)` }"
|
||||
class="ms-0.5 size-2 rounded-full bg-(--chip)"
|
||||
/>
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItem } from '@nuxt/ui'
|
||||
import { useUserStore } from '~~/stores/useUserStore'
|
||||
|
||||
defineProps<{
|
||||
collapsed?: boolean
|
||||
}>()
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const colors = [
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'pink',
|
||||
'rose'
|
||||
]
|
||||
const neutrals = ['slate', 'gray', 'zinc', 'neutral', 'stone']
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { user: keyCloakUser } = storeToRefs(userStore)
|
||||
|
||||
const user = ref({
|
||||
name: keyCloakUser.value.name,
|
||||
avatar: {
|
||||
alt: keyCloakUser.value.name
|
||||
}
|
||||
})
|
||||
|
||||
const items = computed<DropdownMenuItem[][]>(() => [
|
||||
[
|
||||
{
|
||||
type: 'label',
|
||||
label: user.value.name,
|
||||
avatar: user.value.avatar
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'Profile',
|
||||
icon: 'i-lucide-user'
|
||||
},
|
||||
{
|
||||
label: 'Administration',
|
||||
icon: 'i-lucide-user',
|
||||
to: '/administration'
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: 'i-lucide-settings',
|
||||
to: '/settings'
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'Theme',
|
||||
icon: 'i-lucide-palette',
|
||||
children: [
|
||||
{
|
||||
label: 'Primary',
|
||||
slot: 'chip',
|
||||
chip: appConfig.ui.colors.primary,
|
||||
content: {
|
||||
align: 'center',
|
||||
collisionPadding: 16
|
||||
},
|
||||
children: colors.map((color) => ({
|
||||
label: color,
|
||||
chip: color,
|
||||
slot: 'chip',
|
||||
checked: appConfig.ui.colors.primary === color,
|
||||
type: 'checkbox',
|
||||
onSelect: (e) => {
|
||||
e.preventDefault()
|
||||
appConfig.ui.colors.primary = color
|
||||
}
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: 'Neutral',
|
||||
slot: 'chip',
|
||||
chip: appConfig.ui.colors.neutral,
|
||||
content: {
|
||||
align: 'end',
|
||||
collisionPadding: 16
|
||||
},
|
||||
children: neutrals.map((color) => ({
|
||||
label: color,
|
||||
chip: color,
|
||||
slot: 'chip',
|
||||
type: 'checkbox',
|
||||
checked: appConfig.ui.colors.neutral === color,
|
||||
onSelect: (e) => {
|
||||
e.preventDefault()
|
||||
appConfig.ui.colors.neutral = color
|
||||
}
|
||||
}))
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Appearance',
|
||||
icon: 'i-lucide-sun-moon',
|
||||
children: [
|
||||
{
|
||||
label: 'Light',
|
||||
icon: 'i-lucide-sun',
|
||||
type: 'checkbox',
|
||||
checked: colorMode.value === 'light',
|
||||
onSelect(e: Event) {
|
||||
e.preventDefault()
|
||||
colorMode.preference = 'light'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Dark',
|
||||
icon: 'i-lucide-moon',
|
||||
type: 'checkbox',
|
||||
checked: colorMode.value === 'dark',
|
||||
onUpdateChecked(checked: boolean) {
|
||||
if (checked) {
|
||||
colorMode.preference = 'dark'
|
||||
}
|
||||
},
|
||||
onSelect(e: Event) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'Log out',
|
||||
icon: 'i-lucide-log-out',
|
||||
async onSelect(e: Event) {
|
||||
e.preventDefault()
|
||||
await navigateTo('/auth/logout', { external: true })
|
||||
}
|
||||
}
|
||||
]
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<!-- <component :is="getResolvedComponent(formElement)" :model-value="input" @update:model-value="update($event, index)" /> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { FormElementType, type FormOptionDto, type FormElementDto } from '~~/.api-client'
|
||||
// import { resolveComponent } from 'vue'
|
||||
|
||||
// const props = defineProps<{
|
||||
// formElementType: FormElementType
|
||||
// modelValue: FormOptionDto[]
|
||||
// }>()
|
||||
|
||||
// const emit = defineEmits<{
|
||||
// (e: 'update:modelValue', value: FormOptionDto[]): void
|
||||
// }>()
|
||||
|
||||
// // TODO: Lazy loading?
|
||||
// function getResolvedComponent() {
|
||||
// switch (props.formElementType) {
|
||||
// case 'CHECKBOX':
|
||||
// case 'DROPDOWN':
|
||||
// case 'RADIOBUTTON':
|
||||
// case 'SWITCH':
|
||||
// return resolveComponent('TheSwitch')
|
||||
// case 'TEXTFIELD':
|
||||
// return resolveComponent('TheInput')
|
||||
// default:
|
||||
// return resolveComponent('Unimplemented')
|
||||
// }
|
||||
// }
|
||||
|
||||
// const input = computed<FormOptionDto | FormOptionDto[]>({
|
||||
// get: () => {
|
||||
// if (props.formElementType === FormElementType.Switch) {
|
||||
// return props.modelValue[0]
|
||||
// } else {
|
||||
// return props.modelValue
|
||||
// }
|
||||
// },
|
||||
// set: (val) => {
|
||||
// // TODO
|
||||
// if (Array.isArray(val)) {
|
||||
// const updatedModelValue = [...props.modelValue]
|
||||
// updatedModelValue[0] = { ...updatedModelValue[0], value: val.toString() }
|
||||
// emit('update:modelValue', updatedModelValue)
|
||||
// } else {
|
||||
// emit('update:modelValue', val)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
28
legalconsenthub/app/components/formelements/TheCheckbox.vue
Normal file
28
legalconsenthub/app/components/formelements/TheCheckbox.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<UCheckbox v-model="modelValue" :label="label" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
formOptions: FormOptionDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions?.[0].value === 'true',
|
||||
set: (val) => {
|
||||
if (props.formOptions?.[0]) {
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue[0] = { ...updatedModelValue[0], value: val.toString() }
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const label = computed(() => props.formOptions?.[0].label ?? '')
|
||||
</script>
|
||||
29
legalconsenthub/app/components/formelements/TheInput.vue
Normal file
29
legalconsenthub/app/components/formelements/TheInput.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<UFormField :label="label">
|
||||
<UInput v-model="modelValue" />
|
||||
</UFormField>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
formOptions: FormOptionDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions?.[0].value ?? '',
|
||||
set: (val) => {
|
||||
if (val && props.formOptions?.[0].value) {
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue[0] = { ...updatedModelValue[0], value: val.toString() }
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<URadioGroup v-model="modelValue" :items="items" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
formOptions: FormOptionDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
// Our "label" is the "value" of the radio button
|
||||
const items = computed(() => props.formOptions.map((option) => ({ label: option.label, value: option.label })))
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions.find((option) => option.value === 'true')?.label,
|
||||
set: (val) => {
|
||||
if (val) {
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue.forEach((option) => {
|
||||
option.value = option.label === val ? 'true' : 'false'
|
||||
})
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
32
legalconsenthub/app/components/formelements/TheSelect.vue
Normal file
32
legalconsenthub/app/components/formelements/TheSelect.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<USelect v-model="modelValue" placeholder="Select status" :items="items" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
formOptions: FormOptionDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
// Our "label" is the "value" of the select
|
||||
const items = computed(() => props.formOptions.map((option) => ({ label: option.label, value: option.label })))
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions.find((option) => option.value === 'true')?.label,
|
||||
set: (val) => {
|
||||
if (val) {
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue.forEach((option) => {
|
||||
option.value = option.label === val ? 'true' : 'false'
|
||||
})
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
28
legalconsenthub/app/components/formelements/TheSwitch.vue
Normal file
28
legalconsenthub/app/components/formelements/TheSwitch.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<USwitch v-model="modelValue" :label="label" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
formOptions: FormOptionDto[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.formOptions?.[0].value === 'true',
|
||||
set: (val) => {
|
||||
if (props.formOptions?.[0]) {
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue[0] = { ...updatedModelValue[0], value: val.toString() }
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const label = computed(() => props.formOptions?.[0].label ?? '')
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<UFormField label="Titel">
|
||||
<UInput v-model="title" class="w-full" :disabled="props.disabled" />
|
||||
</UFormField>
|
||||
<UFormField label="Text">
|
||||
<UTextarea v-model="body" class="w-full" autoresize :disabled="props.disabled" />
|
||||
</UFormField>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormOptionDto } from '~~/.api-client'
|
||||
|
||||
const props = defineProps<{
|
||||
formOptions: FormOptionDto[]
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:formOptions', value: FormOptionDto[]): void
|
||||
}>()
|
||||
|
||||
const SEPARATOR = '|||'
|
||||
|
||||
const title = computed({
|
||||
get: () => {
|
||||
const currentValue = props.formOptions?.[0]?.value ?? ''
|
||||
return splitValue(currentValue).title
|
||||
},
|
||||
set: (newTitle: string) => {
|
||||
const currentValue = props.formOptions?.[0]?.value ?? ''
|
||||
const { body: currentBody } = splitValue(currentValue)
|
||||
const combinedValue = joinValue(newTitle, currentBody)
|
||||
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue[0] = { ...updatedModelValue[0], value: combinedValue }
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
})
|
||||
|
||||
const body = computed({
|
||||
get: () => {
|
||||
const currentValue = props.formOptions?.[0]?.value ?? ''
|
||||
return splitValue(currentValue).body
|
||||
},
|
||||
set: (newBody: string) => {
|
||||
const currentValue = props.formOptions?.[0]?.value ?? ''
|
||||
const { title: currentTitle } = splitValue(currentValue)
|
||||
const combinedValue = joinValue(currentTitle, newBody)
|
||||
|
||||
const updatedModelValue = [...props.formOptions]
|
||||
updatedModelValue[0] = { ...updatedModelValue[0], value: combinedValue }
|
||||
emit('update:formOptions', updatedModelValue)
|
||||
}
|
||||
})
|
||||
|
||||
function splitValue(value: string): { title: string; body: string } {
|
||||
const parts = value.split(SEPARATOR)
|
||||
return {
|
||||
title: parts[0] || '',
|
||||
body: parts[1] || ''
|
||||
}
|
||||
}
|
||||
function joinValue(title: string, body: string): string {
|
||||
return `${title}${SEPARATOR}${body}`
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Element unimplemented:</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user