feat: add notification store, add label
All checks were successful
continuous-integration/drone/pr Build is passing
All checks were successful
continuous-integration/drone/pr Build is passing
This commit is contained in:
parent
10e044962e
commit
9fe0b5b766
|
@ -1,27 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="notifications">
|
<div class="notifications">
|
||||||
<!-- FIXME: add label -->
|
<NavbarTriggerButton
|
||||||
<slot :togglePopup="togglePopup" :hasUnreadNotifications="hasUnreadNotifications">
|
:pressed="modalIsOpen"
|
||||||
<NavbarTriggerButton
|
ref="toggleButton"
|
||||||
:pressed="showNotifications"
|
:aria-label="modalIsOpen ? $t('notification.hideNotifications') : $t('notification.showNotifications')"
|
||||||
ref="toggleButton"
|
@click="togglePopup"
|
||||||
@click="togglePopup"
|
>
|
||||||
>
|
<span v-if="hasUnreadNotifications" class="unread-indicator" />
|
||||||
<span v-if="hasUnreadNotifications" class="unread-indicator" />
|
<icon icon="bell"/>
|
||||||
<icon icon="bell"/>
|
</NavbarTriggerButton>
|
||||||
</NavbarTriggerButton>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<!-- FIXME: create dedicated dropdown menu -->
|
<!-- FIXME: create dedicated dropdown menu -->
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div class="notifications-list" v-if="showNotifications" ref="popup">
|
<div class="notifications-list" v-if="modalIsOpen" ref="popup">
|
||||||
<h3 class="head">{{ $t('notification.title') }}</h3>
|
<h3 class="head">{{ $t('notification.title') }}</h3>
|
||||||
<NotificationItem
|
<NotificationItem
|
||||||
v-for="(n, index) in notifications"
|
v-for="n in notifications"
|
||||||
:key="n.id"
|
:key="n.id"
|
||||||
class="notification-item"
|
class="notification-item"
|
||||||
:notification="n"
|
:notification="n"
|
||||||
@markNotificationAsRead="markNotificationAsRead(index, n)"
|
@markNotificationAsRead="markNotificationAsRead(n)"
|
||||||
/>
|
/>
|
||||||
<p class="nothing" v-if="notifications.length === 0">
|
<p class="nothing" v-if="notifications.length === 0">
|
||||||
{{ $t('notification.none') }}<br/>
|
{{ $t('notification.none') }}<br/>
|
||||||
|
@ -35,75 +33,42 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, onMounted, onUnmounted, ref} from 'vue'
|
import {ref} from 'vue'
|
||||||
import {onClickOutside} from '@vueuse/core'
|
import {onClickOutside, tryOnUnmounted} from '@vueuse/core'
|
||||||
|
|
||||||
import type {INotification} from '@/modelTypes/INotification'
|
|
||||||
|
|
||||||
import NotificationService from '@/services/notification'
|
|
||||||
import NavbarTriggerButton from '@/components/home/NavbarTriggerButton.vue'
|
import NavbarTriggerButton from '@/components/home/NavbarTriggerButton.vue'
|
||||||
import NotificationItem from '@/components/notifications/NotificationItem.vue'
|
import NotificationItem from '@/components/notifications/NotificationItem.vue'
|
||||||
|
import {useNotificationStore} from '@/stores/notifications'
|
||||||
|
|
||||||
import {findIndexById} from '@/helpers/utils'
|
const modalIsOpen = ref(false)
|
||||||
|
|
||||||
const NOTIFICATIONS_PULL_INTERVAL = 10000
|
|
||||||
|
|
||||||
const allNotifications = ref<INotification[]>([])
|
|
||||||
const showNotifications = ref(false)
|
|
||||||
const popup = ref(null)
|
const popup = ref(null)
|
||||||
const toggleButton = ref(null)
|
const toggleButton = ref(null)
|
||||||
|
|
||||||
function togglePopup() {
|
function togglePopup() {
|
||||||
showNotifications.value = !showNotifications.value
|
modalIsOpen.value = !modalIsOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickOutside(
|
onClickOutside(
|
||||||
popup,
|
popup,
|
||||||
() => {
|
() => {
|
||||||
if (!showNotifications.value) {
|
if (!modalIsOpen.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showNotifications.value = false
|
modalIsOpen.value = false
|
||||||
},
|
},
|
||||||
{ ignore: [toggleButton]},
|
{ ignore: [toggleButton]},
|
||||||
)
|
)
|
||||||
|
|
||||||
const notifications = computed(() => {
|
const {
|
||||||
return allNotifications.value ? allNotifications.value.filter(n => n.name !== '') : []
|
notifications,
|
||||||
})
|
hasUnreadNotifications,
|
||||||
const unreadNotifications = computed(() => {
|
|
||||||
return notifications.value.filter(n => n.readAt === null).length
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasUnreadNotifications = computed(() => unreadNotifications.value > 0)
|
startNotificationPulling,
|
||||||
|
markNotificationAsRead,
|
||||||
|
} = useNotificationStore()
|
||||||
|
|
||||||
let interval: ReturnType<typeof setInterval>
|
const stopNotificationPulling = startNotificationPulling()
|
||||||
onMounted(() => {
|
tryOnUnmounted(stopNotificationPulling)
|
||||||
interval = setInterval(loadNotifications, NOTIFICATIONS_PULL_INTERVAL)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => clearInterval(interval))
|
|
||||||
|
|
||||||
const notificationService = new NotificationService()
|
|
||||||
|
|
||||||
loadNotifications()
|
|
||||||
async function loadNotifications() {
|
|
||||||
allNotifications.value = await notificationService.getAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function markNotificationAsRead(notification: INotification) {
|
|
||||||
const index = findIndexById(allNotifications.value, notification.id)
|
|
||||||
if (index === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
allNotifications.value[index] = {
|
|
||||||
...notification,
|
|
||||||
read: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
await notificationService.update(allNotifications.value[index])
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -943,6 +943,8 @@
|
||||||
"contact": "contact us"
|
"contact": "contact us"
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
|
"showNotifications": "show Notifications",
|
||||||
|
"hideNotifications": "hide Notifications",
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
"none": "You don't have any notifications. Have a nice day!",
|
"none": "You don't have any notifications. Have a nice day!",
|
||||||
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen.",
|
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen.",
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import AbstractService from '@/services/abstractService'
|
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
import NotificationModel from '@/models/notification'
|
|
||||||
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
|
|
||||||
import type {IUser} from '@/modelTypes/IUser'
|
import type {IUser} from '@/modelTypes/IUser'
|
||||||
|
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
|
||||||
|
|
||||||
|
import AbstractService from '@/services/abstractService'
|
||||||
|
import NotificationModel from '@/models/notification'
|
||||||
import {getTextIdentifier} from '@/models/task'
|
import {getTextIdentifier} from '@/models/task'
|
||||||
import {getDisplayName} from '@/models/user'
|
import {getDisplayName} from '@/models/user'
|
||||||
|
|
||||||
import {i18n} from '@/i18n'
|
import {i18n} from '@/i18n'
|
||||||
|
|
||||||
export function getNotificationTitle(notificationItem: INotification, user: IUser | null) {
|
export function getNotificationTitle(notificationItem: INotification, user: IUser | null) {
|
||||||
|
|
66
src/stores/notifications.ts
Normal file
66
src/stores/notifications.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import {computed, ref} from 'vue'
|
||||||
|
import type {INotification} from '@/modelTypes/INotification'
|
||||||
|
import NotificationService from '@/services/notification'
|
||||||
|
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||||
|
import {findIndexById} from '@/helpers/utils'
|
||||||
|
|
||||||
|
const NOTIFICATIONS_PULL_INTERVAL = 10000
|
||||||
|
|
||||||
|
export const useNotificationStore = defineStore('notification', () => {
|
||||||
|
const allNotifications = ref<INotification[]>([])
|
||||||
|
|
||||||
|
const notifications = computed(() => {
|
||||||
|
return allNotifications.value ? allNotifications.value.filter(n => n.name !== '') : []
|
||||||
|
})
|
||||||
|
const unreadNotifications = computed(() => {
|
||||||
|
return notifications.value.filter(n => n.readAt === null).length
|
||||||
|
})
|
||||||
|
const hasUnreadNotifications = computed(() => unreadNotifications.value > 0)
|
||||||
|
|
||||||
|
let interval: ReturnType<typeof setInterval>
|
||||||
|
|
||||||
|
const notificationService = new NotificationService()
|
||||||
|
|
||||||
|
async function loadNotifications() {
|
||||||
|
allNotifications.value = await notificationService.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
function startNotificationPulling() {
|
||||||
|
loadNotifications()
|
||||||
|
interval = setInterval(loadNotifications, NOTIFICATIONS_PULL_INTERVAL)
|
||||||
|
return stopNotificationPulling
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopNotificationPulling() {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markNotificationAsRead(notificationItem: INotification) {
|
||||||
|
const index = findIndexById(allNotifications.value, notificationItem.id)
|
||||||
|
if (index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allNotifications.value[index] = {
|
||||||
|
...notificationItem,
|
||||||
|
read: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
await notificationService.update(allNotifications.value[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications,
|
||||||
|
unreadNotifications,
|
||||||
|
hasUnreadNotifications,
|
||||||
|
|
||||||
|
startNotificationPulling,
|
||||||
|
stopNotificationPulling,
|
||||||
|
markNotificationAsRead,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// support hot reloading
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useNotificationStore, import.meta.hot))
|
||||||
|
}
|
Reference in New Issue
Block a user