feat: add notification store, add label
All checks were successful
continuous-integration/drone/pr Build is passing

This commit is contained in:
Dominik Pschenitschni 2022-11-14 15:31:58 +01:00
parent 10e044962e
commit 9fe0b5b766
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
4 changed files with 101 additions and 65 deletions

View File

@ -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>

View File

@ -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.",

View File

@ -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) {

View 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))
}