diff --git a/src/boot/plugins.ts b/src/boot/plugins.ts index c070353..a6515b9 100644 --- a/src/boot/plugins.ts +++ b/src/boot/plugins.ts @@ -313,7 +313,7 @@ async function getBackend() { */ export default boot(async ({ router, app }) => { const backend = await getBackend(); - if (!backend) { + if (backend === null) { void router.push({ name: 'error' }); return; } diff --git a/src/components/Notification.vue b/src/components/Notification.vue new file mode 100644 index 0000000..74f3bd1 --- /dev/null +++ b/src/components/Notification.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/flaschengeist.d.ts b/src/flaschengeist.d.ts index 11cf79d..ae693ec 100644 --- a/src/flaschengeist.d.ts +++ b/src/flaschengeist.d.ts @@ -1,11 +1,10 @@ declare namespace FG { - interface Session { - expires: Date; - token: string; - lifetime: number; - browser: string; - platform: string; - userid: string; + interface Notification { + id: number; + plugin: string; + text: string; + data?: unknown; + time: Date; } interface User { userid: string; @@ -18,6 +17,14 @@ declare namespace FG { permissions?: Array; avatar_url?: string; } + interface Session { + expires: Date; + token: string; + lifetime: number; + browser: string; + platform: string; + userid: string; + } type Permission = string; interface Role { id: number; @@ -69,6 +76,7 @@ declare namespace FG { } interface Service { userid: string; + is_backup: boolean; value: number; } interface Drink { diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 0cdd70a..6d0e074 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -21,6 +21,25 @@ + {{ notifications.length }} + + + +
Keine neuen Benachrichtigungen
+
+
import EssentialLink from 'src/components/navigation/EssentialLink.vue'; import ShortcutLink from 'src/components/navigation/ShortcutLink.vue'; +import Notification from 'src/components/Notification.vue'; import { Screen } from 'quasar'; -import { defineComponent, ref, inject, computed } from 'vue'; +import { defineComponent, ref, inject, computed, onBeforeMount } from 'vue'; import { useMainStore } from 'src/store'; import { FG_Plugin } from 'src/plugins'; import { useRouter } from 'vue-router'; @@ -100,7 +120,7 @@ const essentials: FG_Plugin.MenuLink[] = [ export default defineComponent({ name: 'MainLayout', - components: { EssentialLink, ShortcutLink }, + components: { EssentialLink, ShortcutLink, Notification }, setup() { const router = useRouter(); const mainStore = useMainStore(); @@ -108,6 +128,16 @@ export default defineComponent({ const leftDrawer = ref(false); const shortcuts = flaschengeist?.shortcuts; const mainLinks = flaschengeist?.menuLinks; + const notifications = computed(() => mainStore.notifications.slice().reverse()); + const noPermission = ref(window.Notification.permission !== 'granted'); + + function requestPermission() { + void window.Notification.requestPermission().then( + (p) => (noPermission.value = p !== 'granted') + ); + } + + onBeforeMount(() => window.setInterval(() => void mainStore.loadNotifications(), 30000)); const leftDrawerOpen = computed({ get: () => (leftDrawer.value || Screen.gt.sm ? true : false), @@ -131,6 +161,10 @@ export default defineComponent({ void mainStore.logout(); } + async function remove(id: number) { + await mainStore.removeNotification(id); + } + return { essentials, leftDrawerOpen, @@ -138,6 +172,10 @@ export default defineComponent({ leftDrawerClicker, logout, mainLinks, + notifications, + noPermission, + remove, + requestPermission, shortcuts, subLinks, }; diff --git a/src/plugins/schedule/components/overview/slots/JobSlot.vue b/src/plugins/schedule/components/overview/slots/JobSlot.vue index a188487..71948e7 100644 --- a/src/plugins/schedule/components/overview/slots/JobSlot.vue +++ b/src/plugins/schedule/components/overview/slots/JobSlot.vue @@ -84,6 +84,7 @@ export default defineComponent({ async function enrollForJob() { const newService: FG.Service = { userid: mainStore.currentUser.userid, + is_backup: false, value: 1, }; try { @@ -104,6 +105,7 @@ export default defineComponent({ async function signOutFromJob() { const newService: FG.Service = { userid: mainStore.currentUser.userid, + is_backup: false, value: -1, }; try { diff --git a/src/store/index.ts b/src/store/index.ts index 21d53f5..211e24e 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -41,6 +41,7 @@ export const useMainStore = defineStore({ state: () => ({ session: loadCurrentSession(), user: loadUser(), + notifications: [] as Array, }), getters: { @@ -123,5 +124,33 @@ export const useMainStore = defineStore({ response && 'status' in response ? (response).status : false ); }, + + async loadNotifications() { + const params = + this.notifications.length > 0 + ? { from: this.notifications[this.notifications.length - 1].time } + : {}; + const { data } = await api.get('/notifications', { params: params }); + data.forEach((n) => { + n.time = new Date(n.time); + if (window.Notification.permission === 'granted') + new window.Notification(n.text, { + timestamp: n.time.getTime(), + }); + }); + this.notifications.push(...data); + }, + + async removeNotification(id: number) { + const idx = this.notifications.findIndex((n) => n.id === id); + if (idx >= 0) + try { + this.notifications.splice(idx, 1); + await api.delete(`/notifications/${id}`); + } catch (error) { + if (this.notifications.length > idx) + this.notifications.splice(idx, this.notifications.length - idx - 1); + } + }, }, });