210 lines
6.9 KiB
Vue
210 lines
6.9 KiB
Vue
<template>
|
|
<UDashboardPanel id="home">
|
|
<template #header>
|
|
<UDashboardNavbar :title="$t('common.home')" :ui="{ right: 'gap-3' }">
|
|
<template #leading>
|
|
<UDashboardSidebarCollapse />
|
|
</template>
|
|
|
|
<template #right>
|
|
{{ $t('organization.current') }}
|
|
<USelect
|
|
v-model="selectedOrganizationId"
|
|
:items="organizations"
|
|
value-key="id"
|
|
label-key="name"
|
|
size="lg"
|
|
:ui="{
|
|
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
|
}"
|
|
class="w-48"
|
|
/>
|
|
|
|
<UTooltip :text="$t('notifications.tooltip')" :shortcuts="['N']">
|
|
<UButton color="neutral" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
|
|
<UChip :show="unreadCount > 0" color="error" inset>
|
|
<UIcon name="i-lucide-bell" class="size-5 shrink-0" />
|
|
<span v-if="unreadCount > 0" class="ml-1 text-xs">{{ unreadCount }}</span>
|
|
</UChip>
|
|
</UButton>
|
|
</UTooltip>
|
|
|
|
<UButton
|
|
icon="i-lucide-circle-plus"
|
|
:label="$t('applicationForms.createNew')"
|
|
to="/create"
|
|
:disabled="!canWriteApplicationForms"
|
|
size="xl"
|
|
variant="outline"
|
|
color="neutral"
|
|
:ui="{
|
|
leadingIcon: 'text-primary'
|
|
}"
|
|
/>
|
|
</template>
|
|
</UDashboardNavbar>
|
|
</template>
|
|
|
|
<template #body>
|
|
<div class="flex flex-col gap-4 sm:gap-6 w-full lg:max-w-4xl mx-auto p-4">
|
|
<UCard
|
|
v-for="(applicationFormElem, index) in applicationForms"
|
|
:key="applicationFormElem.id"
|
|
class="cursor-pointer hover:ring-2 hover:ring-primary transition-all duration-200"
|
|
@click="navigateTo(`application-forms/${applicationFormElem.id}/0`)"
|
|
>
|
|
<template #header>
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="flex-1 min-w-0">
|
|
<h3 class="font-semibold text-lg text-highlighted truncate">
|
|
{{ applicationFormElem.name }}
|
|
</h3>
|
|
<p class="text-xs text-muted mt-1">#{{ index }}</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<UBadge
|
|
:label="applicationFormElem.status"
|
|
:color="getStatusColor(applicationFormElem.status)"
|
|
variant="subtle"
|
|
size="sm"
|
|
/>
|
|
<UDropdownMenu :items="[getLinksForApplicationForm(applicationFormElem)]" :content="{ align: 'end' }">
|
|
<UButton
|
|
icon="i-lucide-ellipsis-vertical"
|
|
color="neutral"
|
|
variant="ghost"
|
|
size="sm"
|
|
square
|
|
@click.stop
|
|
/>
|
|
</UDropdownMenu>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="space-y-3">
|
|
<div class="flex items-center gap-2 text-sm">
|
|
<UIcon name="i-lucide-pencil" class="size-4 text-muted shrink-0" />
|
|
<span class="text-muted">
|
|
{{ $t('applicationForms.lastEditedBy') }}
|
|
<span class="font-medium text-highlighted">{{ applicationFormElem.lastModifiedBy.name }}</span>
|
|
{{ $t('common.on') }} {{ formatDate(applicationFormElem.modifiedAt) }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 text-sm">
|
|
<UIcon name="i-lucide-user-plus" class="size-4 text-muted shrink-0" />
|
|
<span class="text-muted">
|
|
{{ $t('applicationForms.createdBy') }}
|
|
<span class="font-medium text-highlighted">{{ applicationFormElem.createdBy.name }}</span>
|
|
{{ $t('common.on') }} {{ formatDate(applicationFormElem.createdAt) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</template>
|
|
<DeleteModal
|
|
v-if="isDeleteModalOpen && applicationFormNameToDelete"
|
|
v-model:is-open="isDeleteModalOpen"
|
|
:application-form-to-delete="applicationFormNameToDelete"
|
|
@delete="deleteApplicationForm($event)"
|
|
/>
|
|
</UDashboardPanel>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ApplicationFormDto, PagedApplicationFormDto } from '~~/.api-client'
|
|
import type { Organization } from '~~/types/keycloak'
|
|
import { useUserStore } from '~~/stores/useUserStore'
|
|
|
|
const { getAllApplicationForms, deleteApplicationFormById } = useApplicationForm()
|
|
const route = useRoute()
|
|
const userStore = useUserStore()
|
|
const { organizations, selectedOrganization } = storeToRefs(userStore)
|
|
const { t: $t } = useI18n()
|
|
|
|
// Inject notification state from layout
|
|
const { isNotificationsSlideoverOpen, unreadCount } = inject('notificationState', {
|
|
isNotificationsSlideoverOpen: ref(false),
|
|
unreadCount: ref(0)
|
|
})
|
|
|
|
const { data } = await useAsyncData<PagedApplicationFormDto>(
|
|
async () => {
|
|
if (!selectedOrganization.value) {
|
|
throw new Error('No organization selected')
|
|
}
|
|
return await getAllApplicationForms(selectedOrganization.value.id)
|
|
},
|
|
{ watch: [selectedOrganization] }
|
|
)
|
|
|
|
const isDeleteModalOpen = computed<boolean>({
|
|
get: () => 'delete' in route.query,
|
|
set: (isOpen: boolean) => {
|
|
if (isOpen) return
|
|
navigateTo({ path: route.path, query: {} })
|
|
}
|
|
})
|
|
|
|
const applicationFormNameToDelete = computed(() => {
|
|
return data?.value?.content.find((appForm) => appForm.id === route.query.id)
|
|
})
|
|
|
|
const selectedOrganizationId = computed({
|
|
get() {
|
|
return selectedOrganization.value?.id
|
|
},
|
|
set(item) {
|
|
// TODO: USelect triggers multiple times after single selection
|
|
const foundOrg = organizations.value?.find((i: Organization) => i.id === item) ?? null
|
|
if (foundOrg !== undefined) {
|
|
selectedOrganization.value = foundOrg
|
|
}
|
|
}
|
|
})
|
|
|
|
const { canWriteApplicationForms } = usePermissions()
|
|
|
|
const applicationForms = computed({
|
|
get: () => data?.value?.content ?? [],
|
|
set: (val) => {
|
|
if (val && data.value) {
|
|
data.value.content = val
|
|
}
|
|
}
|
|
})
|
|
|
|
function getLinksForApplicationForm(applicationForm: ApplicationFormDto) {
|
|
return [
|
|
{
|
|
label: $t('common.delete'),
|
|
icon: 'i-lucide-trash',
|
|
to: `?delete&id=${applicationForm.id}`,
|
|
disabled: !canWriteApplicationForms.value
|
|
}
|
|
]
|
|
}
|
|
|
|
function getStatusColor(status: string) {
|
|
const statusMap: Record<string, 'success' | 'warning' | 'error' | 'info' | 'neutral'> = {
|
|
COMPLETED: 'success',
|
|
IN_PROGRESS: 'warning',
|
|
DRAFT: 'info',
|
|
REJECTED: 'error',
|
|
PENDING: 'warning'
|
|
}
|
|
return statusMap[status] || 'neutral'
|
|
}
|
|
|
|
async function deleteApplicationForm(applicationFormId: string) {
|
|
await deleteApplicationFormById(applicationFormId)
|
|
data.value?.content.splice(
|
|
data.value?.content.findIndex((appForm) => appForm.id === applicationFormId),
|
|
1
|
|
)
|
|
isDeleteModalOpen.value = false
|
|
}
|
|
</script>
|