| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- <template>
- <v-btn ref="btn" icon size="small" class="ml-2">
- <v-badge
- color="warning"
- offset-x="-4"
- offset-y="17"
- :model-value="unreadNotification.length > 0"
- :content="unreadNotification.length"
- >
- <v-icon class="on-primary"> fa fa-bell </v-icon>
- </v-badge>
- </v-btn>
- <v-tooltip v-if="btn !== null" :activator="btn" location="bottom">
- <span>{{ $t('notification') }}</span>
- </v-tooltip>
- <v-menu
- v-if="btn !== null"
- v-model="isOpen"
- :activator="btn"
- location="bottom left"
- >
- <v-card max-width="400">
- <v-card-title class="bg-neutral text-body-2 font-weight-bold">
- {{ $t('notification') }}
- </v-card-title>
- <v-card-text class="ma-0 pa-0 header-menu">
- <v-list density="compact" :subheader="true" class="pa-0">
- <v-list-item
- v-for="(notification, index) in notifications"
- :key="index"
- :class="
- 'list_item py-3' +
- `${notification.notificationUsers.length === 0 ? ' unread' : ''}`
- "
- >
- <span class="">{{ getMessage(notification) }}</span>
- <template #append>
- <v-icon
- v-if="notification.link"
- icon="mdi:mdi-download"
- class="pt-4"
- @click="download(notification.link)"
- />
- </template>
- </v-list-item>
- <v-divider/>
- <!--suppress VueUnrecognizedDirective -->
- <span v-intersect="onLastNotificationIntersect" />
- <v-row
- v-if="pending"
- class="fill-height mt-3 mb-3"
- align="center"
- justify="center"
- >
- <v-progress-circular indeterminate color="neutral" />
- </v-row>
- </v-list>
- </v-card-text>
- <v-card-actions class="ma-0 pa-0">
- <v-list-item
- :key="$t('all_notification')"
- :href="notificationUrl"
- router
- class="theme-primary"
- style="width: 100%; height: 52px"
- >
- <v-list-item-title class="text-body-2">
- <span v-text="$t('all_notification')" />
- </v-list-item-title>
- </v-list-item>
- </v-card-actions>
- </v-card>
- </v-menu>
- </template>
- <script setup lang="ts">
- import { computed, ref } from 'vue'
- import type { ComputedRef, Ref } from 'vue'
- import { useRepo } from 'pinia-orm'
- import { NOTIFICATION_TYPE } from '~/types/enum/enums'
- import Notification from '~/models/Core/Notification'
- import NotificationUsers from '~/models/Core/NotificationUsers'
- import { useAccessProfileStore } from '~/stores/accessProfile'
- import { useEntityFetch } from '~/composables/data/useEntityFetch'
- import type { AnyJson, Pagination } from '~/types/data'
- import { useEntityManager } from '~/composables/data/useEntityManager'
- import UrlUtils from '~/services/utils/urlUtils'
- import NotificationRepository from '~/stores/repositories/NotificationRepository'
- import Query from '~/services/data/Query'
- import PageFilter from '~/services/data/Filters/PageFilter'
- const accessProfileStore = useAccessProfileStore()
- const isOpen: Ref<boolean> = ref(false)
- const page: Ref<number> = ref(1)
- const itemsPerPage: Ref<number> = ref(5)
- const i18n = useI18n()
- const runtimeConfig = useRuntimeConfig()
- const btn = ref(null)
- const { em } = useEntityManager()
- const { fetchCollection } = useEntityFetch()
- const notificationRepo = useRepo(NotificationRepository)
- const query = new Query(new PageFilter(page, itemsPerPage))
- const {
- data: collection,
- pending,
- refresh,
- } = fetchCollection(Notification, null, query)
- /**
- * On récupère les Notifications via le store
- */
- const notifications: ComputedRef<Array<Notification>> = computed(() => {
- return notificationRepo.getNotifications()
- })
- /**
- * Retourne les notifications non lues
- */
- const unreadNotification: ComputedRef<Array<Notification>> = computed(() => {
- return notificationRepo.getUnreadNotifications()
- })
- /**
- * Les metadata dépendront de la dernière valeur du GET lancé
- */
- const pagination: ComputedRef<Pagination> = computed(() => {
- return collection.value !== null ? collection.value.pagination : {}
- })
- const notificationUrl = UrlUtils.join(
- runtimeConfig.baseUrlAdminLegacy,
- '#/notifications/list/',
- )
- /**
- * L'utilisateur a fait défiler le menu jusqu'à la dernière notification affichée
- * @param isIntersecting
- */
- const onLastNotificationIntersect = (isIntersecting: boolean) => {
- if (isIntersecting) {
- update()
- }
- }
- /**
- * Lorsque l'utilisateur scroll on regarde la nextPage à charger et on le fait que si le pending du fetch est false
- * (si on a fini de télécharger les éléments précédents)
- */
- const update = async () => {
- if (
- !pending.value &&
- pagination.value &&
- pagination.value.next &&
- pagination.value.next > 0
- ) {
- pending.value = true
- page.value = pagination.value.next
- await refresh()
- // Si des notifications n'avaient pas été marquées comme lues, on le fait immédiatement.
- markNotificationsAsRead()
- }
- }
- /**
- * On construit le message qui va devoir s'afficher pour une notification
- * @param notification
- */
- const getMessage = (notification: Notification) => {
- switch (notification.type) {
- case NOTIFICATION_TYPE.FILE:
- return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t(
- 'is_ready_to_be_downloaded',
- )}`
- case NOTIFICATION_TYPE.MESSAGE:
- if (notification.message?.action)
- return `${i18n.t('your_message')} ${
- notification.message?.fileName
- } ${i18n.t('is_ready_to_be')} ${notification.message.action}`
- return `${i18n.t('your_message')} ${
- notification.message?.about ?? ''
- } ${i18n.t('has_been_sent')} `
- case NOTIFICATION_TYPE.SYSTEM:
- if (notification.message?.about)
- return `${i18n.t(notification.message.about)}`
- break
- default:
- return i18n.t(notification.name)
- }
- }
- /**
- * Dès la fermeture du menu, on indique que les notifications non lues, le sont.
- */
- const unwatch = watch(isOpen, (newValue, _) => {
- if (!newValue) {
- markNotificationsAsRead()
- }
- })
- onUnmounted(() => {
- unwatch()
- })
- /**
- * Marque une notification comme lue
- */
- const markNotificationAsRead = (notification: Notification) => {
- if (accessProfileStore.id === null) {
- throw new Error('Current access id is null')
- }
- const notificationUsers = em.newInstance(NotificationUsers, {
- access: `/api/accesses/${accessProfileStore.currentAccessId}`,
- notification: `/api/notifications/${notification.id}`,
- isRead: true,
- })
- em.persist(notificationUsers)
- notification.notificationUsers = ['read']
- em.save(notification)
- }
- /**
- * Marque toutes les notifications non lues comme lues
- */
- const markNotificationsAsRead = () => {
- unreadNotification.value.map((notification: Notification) => {
- return markNotificationAsRead(notification)
- })
- }
- /**
- * Download la cible du lien
- * @param link
- */
- const download = (link: string) => {
- if (accessProfileStore.id === null) {
- throw new Error('Current access id is null')
- }
- // TODO: passer cette logique dans un service ; tester ; voir si possible de réunir avec composables/utils/useDownloadFile.ts
- const path: string = link.split('/api')[1]
- // En switch : https://api.test5.opentalent.fr/api/{accessId}/{switchId}/files/{fileId}/download
- // Sans switch : https://local.api.opentalent.fr/api/{accessId}/files/{fileId}/download
- const url = UrlUtils.join(
- runtimeConfig.baseUrlLegacy,
- 'api',
- String(accessProfileStore.id),
- String(accessProfileStore.switchId || ''),
- path,
- )
- window.open(url)
- }
- </script>
- <style scoped lang="scss">
- .list_item {
- white-space: normal;
- }
- .unread {
- background: rgb(var(--v-theme-neutral-soft, white));
- }
- </style>
|