[notifications] Implemented
This commit is contained in:
parent
c362843c8e
commit
852b1dad03
|
@ -313,7 +313,7 @@ async function getBackend() {
|
||||||
*/
|
*/
|
||||||
export default boot(async ({ router, app }) => {
|
export default boot(async ({ router, app }) => {
|
||||||
const backend = await getBackend();
|
const backend = await getBackend();
|
||||||
if (!backend) {
|
if (backend === null) {
|
||||||
void router.push({ name: 'error' });
|
void router.push({ name: 'error' });
|
||||||
return;
|
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 {
|
declare namespace FG {
|
||||||
interface Session {
|
interface Notification {
|
||||||
expires: Date;
|
id: number;
|
||||||
token: string;
|
plugin: string;
|
||||||
lifetime: number;
|
text: string;
|
||||||
browser: string;
|
data?: unknown;
|
||||||
platform: string;
|
time: Date;
|
||||||
userid: string;
|
|
||||||
}
|
}
|
||||||
interface User {
|
interface User {
|
||||||
userid: string;
|
userid: string;
|
||||||
|
@ -18,6 +17,14 @@ declare namespace FG {
|
||||||
permissions?: Array<string>;
|
permissions?: Array<string>;
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
}
|
}
|
||||||
|
interface Session {
|
||||||
|
expires: Date;
|
||||||
|
token: string;
|
||||||
|
lifetime: number;
|
||||||
|
browser: string;
|
||||||
|
platform: string;
|
||||||
|
userid: string;
|
||||||
|
}
|
||||||
type Permission = string;
|
type Permission = string;
|
||||||
interface Role {
|
interface Role {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -69,6 +76,7 @@ declare namespace FG {
|
||||||
}
|
}
|
||||||
interface Service {
|
interface Service {
|
||||||
userid: string;
|
userid: string;
|
||||||
|
is_backup: boolean;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
interface Drink {
|
interface Drink {
|
||||||
|
|
|
@ -21,6 +21,25 @@
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
|
|
||||||
<!-- Hier kommen die Shortlinks hin -->
|
<!-- 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
|
<shortcut-link
|
||||||
v-for="(shortcut, index) in shortcuts"
|
v-for="(shortcut, index) in shortcuts"
|
||||||
:key="'shortcut' + index"
|
:key="'shortcut' + index"
|
||||||
|
@ -84,8 +103,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import EssentialLink from 'src/components/navigation/EssentialLink.vue';
|
import EssentialLink from 'src/components/navigation/EssentialLink.vue';
|
||||||
import ShortcutLink from 'src/components/navigation/ShortcutLink.vue';
|
import ShortcutLink from 'src/components/navigation/ShortcutLink.vue';
|
||||||
|
import Notification from 'src/components/Notification.vue';
|
||||||
import { Screen } from 'quasar';
|
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 { useMainStore } from 'src/store';
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
@ -100,7 +120,7 @@ const essentials: FG_Plugin.MenuLink[] = [
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MainLayout',
|
name: 'MainLayout',
|
||||||
components: { EssentialLink, ShortcutLink },
|
components: { EssentialLink, ShortcutLink, Notification },
|
||||||
setup() {
|
setup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
|
@ -108,6 +128,16 @@ export default defineComponent({
|
||||||
const leftDrawer = ref(false);
|
const leftDrawer = ref(false);
|
||||||
const shortcuts = flaschengeist?.shortcuts;
|
const shortcuts = flaschengeist?.shortcuts;
|
||||||
const mainLinks = flaschengeist?.menuLinks;
|
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({
|
const leftDrawerOpen = computed({
|
||||||
get: () => (leftDrawer.value || Screen.gt.sm ? true : false),
|
get: () => (leftDrawer.value || Screen.gt.sm ? true : false),
|
||||||
|
@ -131,6 +161,10 @@ export default defineComponent({
|
||||||
void mainStore.logout();
|
void mainStore.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function remove(id: number) {
|
||||||
|
await mainStore.removeNotification(id);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
essentials,
|
essentials,
|
||||||
leftDrawerOpen,
|
leftDrawerOpen,
|
||||||
|
@ -138,6 +172,10 @@ export default defineComponent({
|
||||||
leftDrawerClicker,
|
leftDrawerClicker,
|
||||||
logout,
|
logout,
|
||||||
mainLinks,
|
mainLinks,
|
||||||
|
notifications,
|
||||||
|
noPermission,
|
||||||
|
remove,
|
||||||
|
requestPermission,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
subLinks,
|
subLinks,
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,6 +84,7 @@ export default defineComponent({
|
||||||
async function enrollForJob() {
|
async function enrollForJob() {
|
||||||
const newService: FG.Service = {
|
const newService: FG.Service = {
|
||||||
userid: mainStore.currentUser.userid,
|
userid: mainStore.currentUser.userid,
|
||||||
|
is_backup: false,
|
||||||
value: 1,
|
value: 1,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
@ -104,6 +105,7 @@ export default defineComponent({
|
||||||
async function signOutFromJob() {
|
async function signOutFromJob() {
|
||||||
const newService: FG.Service = {
|
const newService: FG.Service = {
|
||||||
userid: mainStore.currentUser.userid,
|
userid: mainStore.currentUser.userid,
|
||||||
|
is_backup: false,
|
||||||
value: -1,
|
value: -1,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const useMainStore = defineStore({
|
||||||
state: () => ({
|
state: () => ({
|
||||||
session: loadCurrentSession(),
|
session: loadCurrentSession(),
|
||||||
user: loadUser(),
|
user: loadUser(),
|
||||||
|
notifications: [] as Array<FG.Notification>,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -123,5 +124,33 @@ export const useMainStore = defineStore({
|
||||||
response && 'status' in response ? (<AxiosResponse>response).status : false
|
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