feat(#9): Nuxt 4 migration

This commit is contained in:
2025-11-02 18:46:46 +01:00
parent 763b2f7b7f
commit 6d79c710a2
54 changed files with 2904 additions and 1416 deletions

View File

@@ -0,0 +1,111 @@
import type {
CreateApplicationFormDto,
CreateFormElementDto,
ApplicationFormDto,
PagedApplicationFormDto
} from '~~/.api-client'
import { useApplicationFormApi } from './useApplicationFormApi'
export function useApplicationForm() {
const applicationFormApi = useApplicationFormApi()
async function createApplicationForm(
createApplicationFormDto: CreateApplicationFormDto
): Promise<ApplicationFormDto> {
try {
return await applicationFormApi.createApplicationForm(createApplicationFormDto)
} catch (e: unknown) {
console.error('Failed creating application form:', e)
return Promise.reject(e)
}
}
async function getAllApplicationForms(organizationId: string): Promise<PagedApplicationFormDto> {
try {
return await applicationFormApi.getAllApplicationForms(organizationId)
} catch (e: unknown) {
console.error('Failed retrieving application forms:', e, JSON.stringify(e))
return Promise.reject(e)
}
}
async function getApplicationFormById(id: string): Promise<ApplicationFormDto> {
try {
return await applicationFormApi.getApplicationFormById(id)
} catch (e: unknown) {
console.error(`Failed retrieving application form with ID ${id}:`, e)
return Promise.reject(e)
}
}
async function updateApplicationForm(
id?: string,
applicationFormDto?: ApplicationFormDto
): Promise<ApplicationFormDto> {
if (!id || !applicationFormDto) {
return Promise.reject(new Error('ID or application form DTO missing'))
}
try {
return await applicationFormApi.updateApplicationForm(id, applicationFormDto)
} catch (e: unknown) {
console.error(`Failed updating application form with ID ${id}:`, e)
return Promise.reject(e)
}
}
async function deleteApplicationFormById(id: string): Promise<void> {
try {
return await applicationFormApi.deleteApplicationFormById(id)
} catch (e: unknown) {
console.error(`Failed deleting application form with ID ${id}:`, e)
return Promise.reject(e)
}
}
async function submitApplicationForm(id: string): Promise<ApplicationFormDto> {
if (!id) {
return Promise.reject(new Error('ID missing'))
}
try {
return await applicationFormApi.submitApplicationForm(id)
} catch (e: unknown) {
console.error(`Failed submitting application form with ID ${id}:`, e)
return Promise.reject(e)
}
}
async function addFormElementToSection(
applicationFormId: string,
sectionId: string,
createFormElementDto: CreateFormElementDto,
position: number
): Promise<ApplicationFormDto> {
if (!applicationFormId || !sectionId) {
return Promise.reject(new Error('Application form ID or section ID missing'))
}
try {
return await applicationFormApi.addFormElementToSection(
applicationFormId,
sectionId,
createFormElementDto,
position
)
} catch (e: unknown) {
console.error(`Failed adding form element to section ${sectionId}:`, e)
return Promise.reject(e)
}
}
return {
createApplicationForm,
getAllApplicationForms,
getApplicationFormById,
updateApplicationForm,
deleteApplicationFormById,
submitApplicationForm,
addFormElementToSection
}
}

View File

@@ -0,0 +1,80 @@
import {
ApplicationFormApi,
Configuration,
type CreateApplicationFormDto,
type CreateFormElementDto,
type ApplicationFormDto,
type PagedApplicationFormDto
} from '~~/.api-client'
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
import { wrappedFetchWrap } from '~/utils/wrappedFetch'
export function useApplicationFormApi() {
const appBaseUrl = useRuntimeConfig().app.baseURL
const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
const basePath = withoutTrailingSlash(
cleanDoubleSlashes(
import.meta.client
? appBaseUrl + clientProxyBasePath
: useRequestURL().origin + clientProxyBasePath + serverApiBasePath
)
)
const applicationFormApiClient = new ApplicationFormApi(
new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) })
)
async function createApplicationForm(
createApplicationFormDto: CreateApplicationFormDto
): Promise<ApplicationFormDto> {
return applicationFormApiClient.createApplicationForm({ createApplicationFormDto })
}
async function getAllApplicationForms(organizationId: string): Promise<PagedApplicationFormDto> {
return applicationFormApiClient.getAllApplicationForms({ organizationId })
}
async function getApplicationFormById(id: string): Promise<ApplicationFormDto> {
return applicationFormApiClient.getApplicationFormById({ id })
}
async function updateApplicationForm(
id: string,
applicationFormDto: ApplicationFormDto
): Promise<ApplicationFormDto> {
return applicationFormApiClient.updateApplicationForm({ id, applicationFormDto })
}
async function deleteApplicationFormById(id: string): Promise<void> {
return applicationFormApiClient.deleteApplicationForm({ id })
}
async function submitApplicationForm(id: string): Promise<ApplicationFormDto> {
return applicationFormApiClient.submitApplicationForm({ id })
}
async function addFormElementToSection(
applicationFormId: string,
sectionId: string,
createFormElementDto: CreateFormElementDto,
position: number
): Promise<ApplicationFormDto> {
return applicationFormApiClient.addFormElementToSection({
applicationFormId,
sectionId,
createFormElementDto,
position
})
}
return {
createApplicationForm,
getAllApplicationForms,
getApplicationFormById,
updateApplicationForm,
deleteApplicationFormById,
submitApplicationForm,
addFormElementToSection
}
}

View File

@@ -0,0 +1,97 @@
import {
type CreateApplicationFormDto,
type ApplicationFormDto,
type PagedApplicationFormDto,
ResponseError
} from '~~/.api-client'
import { useApplicationFormTemplateApi } from './useApplicationFormTemplateApi'
const currentApplicationForm: Ref<ApplicationFormDto | undefined> = ref()
export async function useApplicationFormTemplate() {
const applicationFormApi = await useApplicationFormTemplateApi()
async function createApplicationFormTemplate(
createApplicationFormDto: CreateApplicationFormDto
): Promise<ApplicationFormDto> {
try {
currentApplicationForm.value = await applicationFormApi.createApplicationFormTemplate(createApplicationFormDto)
return currentApplicationForm.value
} catch (e: unknown) {
if (e instanceof ResponseError) {
console.error('Failed creating application form:', e.response)
} else {
console.error('Failed creating application form:', e)
}
return Promise.reject(e)
}
}
async function getAllApplicationFormTemplates(): Promise<PagedApplicationFormDto> {
try {
return await applicationFormApi.getAllApplicationFormTemplates()
} catch (e: unknown) {
if (e instanceof ResponseError) {
console.error('Failed retrieving application forms:', e.response)
} else {
console.error('Failed retrieving application forms:', e)
}
return Promise.reject(e)
}
}
async function getApplicationFormTemplateById(id: string): Promise<ApplicationFormDto> {
try {
return await applicationFormApi.getApplicationFormTemplateById(id)
} catch (e: unknown) {
if (e instanceof ResponseError) {
console.error(`Failed retrieving application form with ID ${id}:`, e.response)
} else {
console.error(`Failed retrieving application form with ID ${id}:`, e)
}
return Promise.reject(e)
}
}
async function updateApplicationFormTemplate(
id?: string,
applicationFormDto?: ApplicationFormDto
): Promise<ApplicationFormDto> {
if (!id || !applicationFormDto) {
return Promise.reject(new Error('ID or application form DTO missing'))
}
try {
currentApplicationForm.value = await applicationFormApi.updateApplicationFormTemplate(id, applicationFormDto)
return currentApplicationForm.value
} catch (e: unknown) {
if (e instanceof ResponseError) {
console.error(`Failed updating application form with ID ${id}:`, e.response)
} else {
console.error(`Failed updating application form with ID ${id}:`, e)
}
return Promise.reject(e)
}
}
async function deleteApplicationFormTemplateById(id: string): Promise<void> {
try {
return await applicationFormApi.deleteApplicationFormTemplateById(id)
} catch (e: unknown) {
if (e instanceof ResponseError) {
console.error(`Failed deleting application form with ID ${id}:`, e.response)
} else {
console.error(`Failed deleting application form with ID ${id}:`, e)
}
return Promise.reject(e)
}
}
return {
createApplicationFormTemplate,
getAllApplicationFormTemplates,
getApplicationFormTemplateById,
updateApplicationFormTemplate,
deleteApplicationFormTemplateById
}
}

View File

@@ -0,0 +1,54 @@
import { ApplicationFormTemplateApi, Configuration } from '../../../.api-client'
import type { CreateApplicationFormDto, ApplicationFormDto, PagedApplicationFormDto } from '~~/.api-client'
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
import { wrappedFetchWrap } from '~/utils/wrappedFetch'
export async function useApplicationFormTemplateApi() {
const appBaseUrl = useRuntimeConfig().app.baseURL
const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
const basePath = withoutTrailingSlash(
cleanDoubleSlashes(
import.meta.client
? appBaseUrl + clientProxyBasePath
: useRequestURL().origin + clientProxyBasePath + serverApiBasePath
)
)
const applicationFormApiClient = new ApplicationFormTemplateApi(
new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) })
)
async function createApplicationFormTemplate(
createApplicationFormDto: CreateApplicationFormDto
): Promise<ApplicationFormDto> {
return applicationFormApiClient.createApplicationFormTemplate({ createApplicationFormDto })
}
async function getAllApplicationFormTemplates(): Promise<PagedApplicationFormDto> {
return applicationFormApiClient.getAllApplicationFormTemplates()
}
async function getApplicationFormTemplateById(id: string): Promise<ApplicationFormDto> {
return applicationFormApiClient.getApplicationFormTemplateById({ id })
}
async function updateApplicationFormTemplate(
id: string,
applicationFormDto: ApplicationFormDto
): Promise<ApplicationFormDto> {
return applicationFormApiClient.updateApplicationFormTemplate({ id, applicationFormDto })
}
async function deleteApplicationFormTemplateById(id: string): Promise<void> {
return applicationFormApiClient.deleteApplicationFormTemplate({ id })
}
return {
createApplicationFormTemplate,
getAllApplicationFormTemplates,
getApplicationFormTemplateById,
updateApplicationFormTemplate,
deleteApplicationFormTemplateById
}
}

View File

@@ -0,0 +1,47 @@
import { CommentApi, Configuration, type CommentDto, type CreateCommentDto, type PagedCommentDto } from '~~/.api-client'
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
import { wrappedFetchWrap } from '~/utils/wrappedFetch'
export function useCommentApi() {
const appBaseUrl = useRuntimeConfig().app.baseURL
const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
const basePath = withoutTrailingSlash(
cleanDoubleSlashes(
import.meta.client
? appBaseUrl + clientProxyBasePath
: useRequestURL().origin + clientProxyBasePath + serverApiBasePath
)
)
const commentApiClient = new CommentApi(
new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) })
)
async function createComment(
applicationFormId: string,
formElementId: string,
createCommentDto: CreateCommentDto
): Promise<CommentDto> {
return commentApiClient.createComment({ applicationFormId, formElementId, createCommentDto })
}
async function getCommentsByApplicationFormId(applicationFormId: string): Promise<PagedCommentDto> {
return commentApiClient.getCommentsByApplicationFormId({ applicationFormId })
}
async function updateComment(id: string, commentDto: CommentDto): Promise<CommentDto> {
return commentApiClient.updateComment({ id, commentDto })
}
async function deleteCommentById(id: string): Promise<void> {
return commentApiClient.deleteComment({ id })
}
return {
createComment,
getCommentsByApplicationFormId,
updateComment,
deleteCommentById
}
}

View File

@@ -0,0 +1,69 @@
import type { CreateCommentDto, CommentDto } from '~~/.api-client'
import { useCommentStore } from '~~/stores/useCommentStore'
import { useUserStore } from '~~/stores/useUserStore'
export function useCommentTextarea(applicationFormId: string) {
const commentStore = useCommentStore()
const { createComment, updateComment } = commentStore
const userStore = useUserStore()
const { user } = storeToRefs(userStore)
const isEditingComment = ref(false)
const currentEditedComment = ref<CommentDto | null>(null)
const commentTextAreaValue = ref('')
const toast = useToast()
async function submitComment(formElementId: string) {
const newCommentDto: CreateCommentDto = {
message: commentTextAreaValue.value
}
try {
await createComment(applicationFormId, formElementId, newCommentDto)
commentTextAreaValue.value = ''
toast.add({ title: 'Comment created successfully', color: 'success' })
} catch (e) {
toast.add({ title: 'Error creating comment', color: 'error' })
console.error('Error creating comment:', e)
}
}
async function updateEditComment() {
if (!currentEditedComment.value) return
const updatedComment: CommentDto = { ...currentEditedComment.value, message: commentTextAreaValue.value }
try {
await updateComment(currentEditedComment.value.id, updatedComment)
commentTextAreaValue.value = ''
currentEditedComment.value = null
isEditingComment.value = false
toast.add({ title: 'Comment updated successfully', color: 'success' })
} catch (e) {
toast.add({ title: 'Error updating comment', color: 'error' })
console.error('Error updating comment:', e)
}
}
function editComment(comment: CommentDto) {
isEditingComment.value = true
currentEditedComment.value = comment
commentTextAreaValue.value = comment.message || ''
}
function cancelEditComment() {
isEditingComment.value = false
currentEditedComment.value = null
commentTextAreaValue.value = ''
}
function isCommentByUser(comment: CommentDto) {
return comment.createdBy.keycloakId === user.value?.keycloakId
}
return {
commentTextAreaValue,
submitComment,
updateEditComment,
editComment,
cancelEditComment,
isEditingComment,
isCommentByUser
}
}

View File

@@ -0,0 +1,47 @@
import { ComplianceStatus, EmployeeDataCategory, FormElementType, ProcessingPurpose } from '~~/.api-client'
export const complianceMap = new Map<ProcessingPurpose, Map<EmployeeDataCategory, ComplianceStatus>>([
[
ProcessingPurpose.SystemOperation,
new Map([
[EmployeeDataCategory.None, ComplianceStatus.NonCritical],
[EmployeeDataCategory.NonCritical, ComplianceStatus.NonCritical],
[EmployeeDataCategory.ReviewRequired, ComplianceStatus.NonCritical],
[EmployeeDataCategory.Sensitive, ComplianceStatus.NonCritical]
])
],
[
ProcessingPurpose.BusinessProcess,
new Map([
[EmployeeDataCategory.None, ComplianceStatus.NonCritical],
[EmployeeDataCategory.NonCritical, ComplianceStatus.NonCritical],
[EmployeeDataCategory.ReviewRequired, ComplianceStatus.Warning],
[EmployeeDataCategory.Sensitive, ComplianceStatus.Critical]
])
],
[
ProcessingPurpose.DataAnalysis,
new Map([
[EmployeeDataCategory.None, ComplianceStatus.NonCritical],
[EmployeeDataCategory.NonCritical, ComplianceStatus.Warning],
[EmployeeDataCategory.ReviewRequired, ComplianceStatus.Warning],
[EmployeeDataCategory.Sensitive, ComplianceStatus.Critical]
])
],
[
ProcessingPurpose.None,
new Map([
[EmployeeDataCategory.None, ComplianceStatus.NonCritical],
[EmployeeDataCategory.NonCritical, ComplianceStatus.NonCritical],
[EmployeeDataCategory.ReviewRequired, ComplianceStatus.NonCritical],
[EmployeeDataCategory.Sensitive, ComplianceStatus.NonCritical]
])
]
])
export const complianceCheckableElementTypes: FormElementType[] = [
FormElementType.Switch,
FormElementType.Select,
FormElementType.Checkbox,
FormElementType.Radiobutton
]

View File

@@ -0,0 +1,4 @@
export { useApplicationFormTemplate } from './applicationFormTemplate/useApplicationFormTemplate'
export { useApplicationForm } from './applicationForm/useApplicationForm'
export { useNotification } from './notification/useNotification'
export { useNotificationApi } from './notification/useNotificationApi'

View File

@@ -0,0 +1,144 @@
import type { NotificationDto } from '~~/.api-client'
import { useUserStore } from '~~/stores/useUserStore'
export const useNotification = () => {
const {
getNotifications,
getUnreadNotifications,
getUnreadNotificationCount,
markAllNotificationsAsRead,
markNotificationAsRead
} = useNotificationApi()
const userStore = useUserStore()
const organizationId = computed(() => userStore.selectedOrganization?.id)
const { user } = useUserSession()
const userId = computed(() => user.value?.keycloakId)
const notifications = ref<NotificationDto[]>([])
const unreadNotifications = ref<NotificationDto[]>([])
const unreadCount = ref<number>(0)
const isLoading = ref(false)
const fetchNotifications = async (page: number = 0, size: number = 20) => {
if (!organizationId.value) {
console.warn('No organization selected')
return
}
isLoading.value = true
try {
const response = await getNotifications(organizationId.value, page, size)
notifications.value = response.content || []
return response
} catch (error) {
console.error('Failed to fetch notifications:', error)
throw error
} finally {
isLoading.value = false
}
}
const fetchUnreadNotifications = async () => {
if (!organizationId.value) {
console.warn('No organization selected')
return
}
try {
const response = await getUnreadNotifications(organizationId.value)
unreadNotifications.value = response || []
return response
} catch (error) {
console.error('Failed to fetch unread notifications:', error)
throw error
}
}
const fetchUnreadCount = async () => {
if (!userId.value || !organizationId.value) {
console.warn('No user or organization selected')
return
}
try {
const count = await getUnreadNotificationCount(userId.value, organizationId.value)
unreadCount.value = count || 0
return count
} catch (error) {
console.error('Failed to fetch unread count:', error)
throw error
}
}
const markAllAsRead = async () => {
if (!organizationId.value) {
console.warn('No organization selected')
return
}
try {
await markAllNotificationsAsRead(organizationId.value)
unreadCount.value = 0
unreadNotifications.value = []
notifications.value = notifications.value.map((n) => ({ ...n, isRead: true }))
} catch (error) {
console.error('Failed to mark all as read:', error)
throw error
}
}
const markAsRead = async (notificationId: string) => {
if (!organizationId.value) {
console.warn('No organization selected')
return
}
try {
await markNotificationAsRead(notificationId, organizationId.value)
const index = notifications.value.findIndex((n) => n.id === notificationId)
if (index !== -1) {
notifications.value[index].isRead = true
}
// Remove from unread notifications
unreadNotifications.value = unreadNotifications.value.filter((n) => n.id !== notificationId)
if (unreadCount.value > 0) {
unreadCount.value--
}
} catch (error) {
console.error('Failed to mark notification as read:', error)
throw error
}
}
const handleNotificationClick = async (notification: NotificationDto) => {
if (!notification.isRead) {
await markAsRead(notification.id)
}
if (notification.clickTarget) {
await navigateTo(notification.clickTarget)
}
}
const startPeriodicRefresh = (intervalMs: number = 30000) => {
const interval = setInterval(() => {
void fetchUnreadCount()
}, intervalMs)
onUnmounted(() => {
clearInterval(interval)
})
return interval
}
return {
notifications,
unreadNotifications,
unreadCount,
isLoading,
fetchNotifications,
fetchUnreadNotifications,
fetchUnreadCount,
markAllAsRead,
markAsRead,
handleNotificationClick,
startPeriodicRefresh
}
}

View File

@@ -0,0 +1,64 @@
import {
NotificationApi,
Configuration,
type NotificationDto,
type PagedNotificationDto,
type CreateNotificationDto
} from '~~/.api-client'
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
import { wrappedFetchWrap } from '~/utils/wrappedFetch'
export function useNotificationApi() {
const appBaseUrl = useRuntimeConfig().app.baseURL
const { serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
const basePath = withoutTrailingSlash(
cleanDoubleSlashes(
import.meta.client
? appBaseUrl + clientProxyBasePath
: useRequestURL().origin + clientProxyBasePath + serverApiBasePath
)
)
const notificationApiClient = new NotificationApi(
new Configuration({ basePath, fetchApi: wrappedFetchWrap(useRequestFetch()) })
)
async function createNotification(createNotificationDto: CreateNotificationDto): Promise<NotificationDto> {
return notificationApiClient.createNotification({ createNotificationDto })
}
async function getNotifications(organizationId: string, page?: number, size?: number): Promise<PagedNotificationDto> {
return notificationApiClient.getNotifications({ organizationId, page, size })
}
async function getUnreadNotifications(organizationId: string): Promise<NotificationDto[]> {
return notificationApiClient.getUnreadNotifications({ organizationId })
}
async function getUnreadNotificationCount(userId: string, organizationId: string): Promise<number> {
return notificationApiClient.getUnreadNotificationCount({ userId, organizationId })
}
async function markAllNotificationsAsRead(organizationId: string): Promise<void> {
return notificationApiClient.markAllNotificationsAsRead({ organizationId })
}
async function markNotificationAsRead(id: string, organizationId: string): Promise<NotificationDto> {
return notificationApiClient.markNotificationAsRead({ id, organizationId })
}
async function clearAllNotifications(organizationId: string): Promise<void> {
return notificationApiClient.clearAllNotifications({ organizationId })
}
return {
createNotification,
getNotifications,
getUnreadNotifications,
getUnreadNotificationCount,
markAllNotificationsAsRead,
markNotificationAsRead,
clearAllNotifications
}
}

View File

@@ -0,0 +1,62 @@
import { ComplianceStatus, type FormElementDto } from '~~/.api-client'
import { complianceCheckableElementTypes, complianceMap } from './complianceMap'
import type { FormElementId } from '~~/types/formElement'
const formElementComplianceMap = ref(new Map<FormElementId, ComplianceStatus>())
export function useApplicationFormValidator() {
function getHighestComplianceStatus(): ComplianceStatus {
const complianceStatusValues = Array.from(formElementComplianceMap.value.values())
const highestComplianceNumber = Math.max(
...complianceStatusValues.map((complianceStatus) => Object.values(ComplianceStatus).indexOf(complianceStatus))
)
return Object.values(ComplianceStatus)[highestComplianceNumber]
}
function validateFormElements(formElements: FormElementDto[]): Map<FormElementId, ComplianceStatus> {
formElementComplianceMap.value.clear()
formElements.forEach((formElement) => {
if (!complianceCheckableElementTypes.includes(formElement.type)) return
// Reset any previously set compliance status when all options are false
const hasAtLeastOneOptionSet = formElement.options.some((option) => option.value && option.value !== 'false')
if (!hasAtLeastOneOptionSet) {
// No value set, continue with next form element
return
}
formElement.options.forEach((option) => {
if (!option.value) {
console.log(`Value missing for ${formElement.type}`)
return
}
// Value not set to true, continue with next option
if (option.value === 'false') {
return
}
const currentHighestComplianceStatus =
complianceMap?.get(option.processingPurpose)?.get(option.employeeDataCategory) ?? ComplianceStatus.NonCritical
const currentHighestComplianceStatusPos =
Object.values(ComplianceStatus).indexOf(currentHighestComplianceStatus)
if (formElementComplianceMap.value.has(formElement.id)) {
const newComplianceStatus = formElementComplianceMap.value.get(formElement.id)!
const newComplianceStatusPos = Object.values(ComplianceStatus).indexOf(newComplianceStatus)
if (newComplianceStatusPos > currentHighestComplianceStatusPos) {
formElementComplianceMap.value.set(formElement.id, newComplianceStatus)
}
} else {
formElementComplianceMap.value.set(formElement.id, currentHighestComplianceStatus)
}
})
})
return formElementComplianceMap.value
}
return { getHighestComplianceStatus, validateFormElements }
}

View File

@@ -0,0 +1,47 @@
import type { ApplicationFormDto, CreateFormElementDto, FormElementSectionDto } from '~~/.api-client'
import type { MaybeRefOrGetter } from 'vue'
export function useFormElementManagement(
currentFormElementSection: MaybeRefOrGetter<FormElementSectionDto | undefined>,
applicationFormId?: string
) {
const { addFormElementToSection } = useApplicationForm()
async function addInputFormToApplicationForm(position: number): Promise<ApplicationFormDto | undefined> {
const section = toValue(currentFormElementSection)
if (!section) return
const { formElements } = section
const inputFormElement: CreateFormElementDto = {
title: 'Formular ergänzen',
description: 'Bitte fügen Sie hier Ihre Ergänzungen ein.',
options: [
{
value: '|||',
label: '',
processingPurpose: 'NONE',
employeeDataCategory: 'NONE'
}
],
type: 'TITLE_BODY_TEXTFIELDS'
}
if (applicationFormId) {
try {
return await addFormElementToSection(applicationFormId, section.id, inputFormElement, position + 1)
} catch (error) {
console.error('Failed to add form element:', error)
throw error
}
} else {
// @ts-expect-error Add CreateFormElementDto to formElements array. ID will be generated by the backend.
formElements.splice(position + 1, 0, inputFormElement)
return undefined
}
}
return {
addInputFormToApplicationForm
}
}

View File

@@ -0,0 +1,57 @@
import type { FormElementSectionDto } from '~~/.api-client'
import type { StepperItem } from '@nuxt/ui'
import type { MaybeRefOrGetter } from 'vue'
interface Stepper {
hasPrev: boolean
hasNext: boolean
next: () => void
prev: () => void
}
export function useFormStepper(
formElementSections: MaybeRefOrGetter<FormElementSectionDto[] | undefined>,
options?: {
onNavigate?: (direction: 'forward' | 'backward', newIndex: number) => void | Promise<void>
}
) {
const stepper = useTemplateRef<Stepper>('stepper')
const activeStepperItemIndex = ref<number>(0)
const sections = computed(() => toValue(formElementSections) ?? [])
const stepperItems = computed(() => {
const items: StepperItem[] = []
sections.value.forEach((section: FormElementSectionDto) => {
items.push({
title: section.shortTitle,
description: section.description
})
})
return items
})
const currentFormElementSection = computed<FormElementSectionDto | undefined>(
() => sections.value[activeStepperItemIndex.value]
)
async function navigateStepper(direction: 'forward' | 'backward') {
if (direction === 'forward') {
stepper.value?.next()
} else {
stepper.value?.prev()
}
if (options?.onNavigate) {
await options.onNavigate(direction, activeStepperItemIndex.value)
}
}
return {
stepper,
activeStepperItemIndex,
stepperItems,
currentFormElementSection,
navigateStepper
}
}

View File

@@ -0,0 +1,152 @@
export type Permission =
| 'application-form:read'
| 'application-form:write'
| 'application-form:sign'
| 'application-form-template:add'
| 'application-form-template:edit'
| 'application-form-template:delete'
| 'comment:add'
| 'comment:edit'
| 'comment:delete'
export type Role =
| 'CHIEF_EXECUTIVE_OFFICER'
| 'BUSINESS_DEPARTMENT'
| 'IT_DEPARTMENT'
| 'HUMAN_RESOURCES'
| 'HEAD_OF_WORKS_COUNCIL'
| 'WORKS_COUNCIL'
| 'EMPLOYEE'
const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
CHIEF_EXECUTIVE_OFFICER: [
'application-form:read',
'application-form:write',
'application-form:sign',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
HEAD_OF_WORKS_COUNCIL: [
'application-form:read',
'application-form:write',
'application-form:sign',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
BUSINESS_DEPARTMENT: [
'application-form:read',
'application-form:write',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
IT_DEPARTMENT: [
'application-form:read',
'application-form:write',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
HUMAN_RESOURCES: [
'application-form:read',
'application-form:write',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
WORKS_COUNCIL: [
'application-form:read',
'application-form:write',
'application-form-template:add',
'application-form-template:edit',
'application-form-template:delete',
'comment:add',
'comment:edit',
'comment:delete'
],
EMPLOYEE: ['application-form:read', 'comment:add', 'comment:edit']
}
export const usePermissions = () => {
const { user } = useUserSession()
const userRoles = computed<Role[]>(() => {
return (user.value?.roles ?? []) as Role[]
})
const userPermissions = computed<Permission[]>(() => {
const permissions = new Set<Permission>()
userRoles.value.forEach((role) => {
const rolePermissions = ROLE_PERMISSIONS[role] ?? []
rolePermissions.forEach((permission) => permissions.add(permission))
})
return Array.from(permissions)
})
const hasPermission = (permission: Permission): boolean => {
return userPermissions.value.includes(permission)
}
const hasAnyPermission = (permissions: Permission[]): boolean => {
return permissions.some((permission) => hasPermission(permission))
}
const hasAllPermissions = (permissions: Permission[]): boolean => {
return permissions.every((permission) => hasPermission(permission))
}
const hasRole = (role: Role): boolean => {
return userRoles.value.includes(role)
}
const hasAnyRole = (roles: Role[]): boolean => {
return roles.some((role) => hasRole(role))
}
const canReadApplicationForms = computed(() => hasPermission('application-form:read'))
const canWriteApplicationForms = computed(() => hasPermission('application-form:write'))
const canSignApplicationForms = computed(() => hasPermission('application-form:sign'))
const canAddTemplate = computed(() => hasPermission('application-form-template:add'))
const canEditTemplate = computed(() => hasPermission('application-form-template:edit'))
const canDeleteTemplate = computed(() => hasPermission('application-form-template:delete'))
const canAddComment = computed(() => hasPermission('comment:add'))
const canEditComment = computed(() => hasPermission('comment:edit'))
const canDeleteComment = computed(() => hasPermission('comment:delete'))
return {
userRoles,
userPermissions,
hasPermission,
hasAnyPermission,
hasAllPermissions,
hasRole,
hasAnyRole,
canReadApplicationForms,
canWriteApplicationForms,
canSignApplicationForms,
canAddTemplate,
canEditTemplate,
canDeleteTemplate,
canAddComment,
canEditComment,
canDeleteComment
}
}

View File

@@ -0,0 +1,92 @@
export const isServerAvailable = ref(true)
export const isChecking = ref(false)
export const lastCheckTime = ref<Date | null>(null)
export function useServerHealth() {
const checkInterval = ref<NodeJS.Timeout | null>(null)
const healthCheckUrl = '/api/actuator/health'
async function checkServerHealth(): Promise<boolean> {
if (isChecking.value) return isServerAvailable.value
isChecking.value = true
lastCheckTime.value = new Date()
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
const response = await fetch(healthCheckUrl, {
method: 'GET',
signal: controller.signal,
headers: {
'Content-Type': 'application/json'
}
})
clearTimeout(timeoutId)
const wasAvailable = isServerAvailable.value
isServerAvailable.value = response.ok
if (!wasAvailable && isServerAvailable.value) {
console.info('Server is back online')
}
if (wasAvailable && !isServerAvailable.value) {
console.warn('Server is no longer available')
}
return isServerAvailable.value
} catch (error) {
const wasAvailable = isServerAvailable.value
isServerAvailable.value = false
if (wasAvailable) {
console.warn('Server health check failed:', error)
}
return false
} finally {
isChecking.value = false
}
}
async function startPeriodicHealthCheck(intervalMs: number = 60000) {
if (checkInterval.value) {
clearInterval(checkInterval.value)
}
checkServerHealth()
checkInterval.value = setInterval(() => {
checkServerHealth()
}, intervalMs)
onUnmounted(() => {
if (checkInterval.value) {
clearInterval(checkInterval.value)
checkInterval.value = null
}
})
return checkInterval.value
}
const stopHealthCheck = () => {
if (checkInterval.value) {
clearInterval(checkInterval.value)
checkInterval.value = null
}
}
return {
isServerAvailable,
isChecking,
lastCheckTime,
healthCheckUrl,
checkServerHealth,
startPeriodicHealthCheck,
stopHealthCheck
}
}