[notifications] Implemented
This commit is contained in:
parent
c362843c8e
commit
852b1dad03
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<q-card bordered class="row q-ma-xs q-pa-xs" style="position: relative">
|
||||
<div class="col-12 text-weight-light">{{ dateString }}</div>
|
||||
<div class="col-12">{{ modelValue.text }}</div>
|
||||
<q-btn
|
||||
round
|
||||
dense
|
||||
icon="close"
|
||||
size="sm"
|
||||
color="negative"
|
||||
class="q-ma-xs"
|
||||
style="position: absolute; top: 0; right: 0"
|
||||
@click="remove"
|
||||
/>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { formatDateTime } from 'src/utils/datetime';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Notification',
|
||||
props: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
type: Object as PropType<FG.Notification>,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
remove: (id: number) => !!id,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const dateString = computed(() => formatDateTime(props.modelValue.time, true, true));
|
||||
|
||||
function remove() {
|
||||
emit('remove', props.modelValue.id);
|
||||
}
|
||||
|
||||
return { dateString, remove };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -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<string>;
|
||||
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 {
|
||||
|
|
|
@ -21,6 +21,25 @@
|
|||
</q-toolbar-title>
|
||||
|
||||
<!-- Hier kommen die Shortlinks hin -->
|
||||
<q-btn icon="message" flat dense
|
||||
><q-badge color="negative" floating>{{ notifications.length }}</q-badge>
|
||||
<q-menu style="max-height: 400px; overflow: auto">
|
||||
<q-btn
|
||||
v-if="noPermission"
|
||||
label="Benachrichtigungen erlauben"
|
||||
@click="requestPermission"
|
||||
/>
|
||||
<template v-if="notifications.length > 0">
|
||||
<Notification
|
||||
v-for="(notification, index) in notifications"
|
||||
:key="index"
|
||||
:model-value="notification"
|
||||
@remove="remove"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="q-pa-sm">Keine neuen Benachrichtigungen</div>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
<shortcut-link
|
||||
v-for="(shortcut, index) in shortcuts"
|
||||
:key="'shortcut' + index"
|
||||
|
@ -84,8 +103,9 @@
|
|||
<script lang="ts">
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -41,6 +41,7 @@ export const useMainStore = defineStore({
|
|||
state: () => ({
|
||||
session: loadCurrentSession(),
|
||||
user: loadUser(),
|
||||
notifications: [] as Array<FG.Notification>,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
@ -123,5 +124,33 @@ export const useMainStore = defineStore({
|
|||
response && 'status' in response ? (<AxiosResponse>response).status : false
|
||||
);
|
||||
},
|
||||
|
||||
async loadNotifications() {
|
||||
const params =
|
||||
this.notifications.length > 0
|
||||
? { from: this.notifications[this.notifications.length - 1].time }
|
||||
: {};
|
||||
const { data } = await api.get<FG.Notification[]>('/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);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue