Added Dashboard as start page

* Plugins can register widgets on the dashboard
* Added dummy widget for schedule and user ("greeting")
* Added simple widget for balance
This commit is contained in:
Ferdinand Thiessen 2020-11-10 01:33:55 +01:00
parent 31620f9681
commit cfc46dddd3
17 changed files with 207 additions and 62 deletions

10
package-lock.json generated
View File

@ -1829,6 +1829,11 @@
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=", "integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=",
"dev": true "dev": true
}, },
"@types/crypto-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.0.1.tgz",
"integrity": "sha512-6+OPzqhKX/cx5xh+yO8Cqg3u3alrkhoxhE5ZOdSEv0DOzJ13lwJ6laqGU0Kv6+XDMFmlnGId04LtY22PsFLQUw=="
},
"@types/electron-packager": { "@types/electron-packager": {
"version": "14.0.0", "version": "14.0.0",
"resolved": "https://registry.npmjs.org/@types/electron-packager/-/electron-packager-14.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/electron-packager/-/electron-packager-14.0.0.tgz",
@ -4402,6 +4407,11 @@
"randomfill": "^1.0.3" "randomfill": "^1.0.3"
} }
}, },
"crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
},
"css": { "css": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",

View File

@ -11,9 +11,11 @@
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.9.10", "@quasar/extras": "^1.9.10",
"@types/crypto-js": "^4.0.1",
"@vue/composition-api": "^0.6.4", "@vue/composition-api": "^0.6.4",
"axios": "^0.18.1", "axios": "^0.18.1",
"core-js": "^3.7.0", "core-js": "^3.7.0",
"crypto-js": "^4.0.0",
"quasar": "^1.14.3", "quasar": "^1.14.3",
"vue-router": "3.3.2" "vue-router": "3.3.2"
}, },

View File

@ -37,7 +37,7 @@ export default boot<Store<StateInterface>>(({ router, store }) => {
} else { } else {
if (store.state.user.currentUser && !to.params['logout']) { if (store.state.user.currentUser && !to.params['logout']) {
// Called login while already logged in // Called login while already logged in
void next({ name: 'user-main' }); void next({ name: 'dashboard' });
} else { } else {
// Not logged in or from logout // Not logged in or from logout
next(); next();

View File

@ -9,7 +9,7 @@ const config = {
// Do not change required Modules !! // Do not change required Modules !!
requiredModules: ['User'], requiredModules: ['User'],
// here you can import plugins. // here you can import plugins.
loadModules: ['Balance'] loadModules: ['Balance', 'Schedule']
}; };
// do not change anything here !! // do not change anything here !!
@ -164,6 +164,9 @@ function loadPlugin(
plugin.outRoutes plugin.outRoutes
); );
} }
if (plugin.widget) {
loadedPlugins.widgets.push(plugin.widget);
}
if (plugin.store) { if (plugin.store) {
console.log(plugin.store); console.log(plugin.store);
console.log(plugin.store.keys()); console.log(plugin.store.keys());
@ -176,7 +179,7 @@ function loadPlugin(
version: plugin.version version: plugin.version
}); });
} else { } else {
console.exception(`Don't find required Plugin ${requiredModule}`); console.exception(`Could not find required Plugin ${requiredModule}`);
} }
}); });
return loadedPlugins; return loadedPlugins;
@ -191,7 +194,8 @@ export default boot<Store<StateInterface>>(({ Vue, router, store }) => {
plugins: [], plugins: [],
mainLinks: [], mainLinks: [],
shortcuts: [], shortcuts: [],
shortcutsOut: [] shortcutsOut: [],
widgets: []
}; };
// get all plugins // get all plugins
@ -210,7 +214,7 @@ export default boot<Store<StateInterface>>(({ Vue, router, store }) => {
); );
loadedPlugins = loadPlugin(loadedPlugins, config.loadModules, plugins, store); loadedPlugins = loadPlugin(loadedPlugins, config.loadModules, plugins, store);
console.log(loadedPlugins.routes); loadedPlugins.widgets.sort((a, b) => b.priority - a.priority);
// add new routes for plugins // add new routes for plugins
router.addRoutes(loadedPlugins.routes); router.addRoutes(loadedPlugins.routes);

View File

@ -13,12 +13,17 @@
/> />
<q-toolbar-title> <q-toolbar-title>
<q-avatar> <router-link
<img src="logo.svg" /> :to="{ name: 'dashboard' }"
</q-avatar> style="text-decoration: none; color: inherit;"
<span class="gt-xs"> >
Flaschengeist <q-avatar>
</span> <img src="logo.svg" />
</q-avatar>
<span class="gt-xs">
Flaschengeist
</span>
</router-link>
</q-toolbar-title> </q-toolbar-title>
<!-- Hier kommen die Shortlinks hin --> <!-- Hier kommen die Shortlinks hin -->

36
src/pages/Dashboard.vue Normal file
View File

@ -0,0 +1,36 @@
<template>
<q-page
padding
style="grid-auto-rows: 1fr;"
class="fit row justify-around items-start q-col-gutter-sm"
>
<div
v-for="(item, index) in widgets"
:key="index"
class="col-4 full-height col-sm-6 col-xs-12"
>
<component v-bind:is="item" />
</div>
</q-page>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from '@vue/composition-api';
import { AsyncComponentPromise } from 'vue/types/options';
export default defineComponent({
name: 'Dashboard',
setup(_, { root }) {
const widgets = ref<Array<AsyncComponentPromise>>([]);
onMounted(() => {
console.log('mounted!');
root.$flaschengeistPlugins.widgets.forEach(widget =>
widgets.value.push(widget.widget)
);
});
return {
widgets
};
}
});
</script>

View File

@ -40,7 +40,7 @@ import { Loading } from 'quasar';
export default defineComponent({ export default defineComponent({
// name: 'PageName' // name: 'PageName'
setup(_, { root }) { setup(_, { root }) {
const mainRoute = { name: 'user-main' }; const mainRoute = { name: 'dashboard' };
/* Stuff for the real login page */ /* Stuff for the real login page */
const userid = ref(''); const userid = ref('');

9
src/plugins.d.ts vendored
View File

@ -1,6 +1,8 @@
import { RouteConfig } from 'vue-router'; import { RouteConfig } from 'vue-router';
import { Module } from 'vuex'; import { Module } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
import { AsyncComponentPromise } from 'vue/types/options';
declare namespace FG_Plugin { declare namespace FG_Plugin {
interface ShortCutLink { interface ShortCutLink {
link: string; link: string;
@ -21,6 +23,7 @@ declare namespace FG_Plugin {
mainRoutes?: PluginRouteConfig[]; mainRoutes?: PluginRouteConfig[];
outRoutes?: PluginRouteConfig[]; outRoutes?: PluginRouteConfig[];
store?: Map<string, Module<any, StateInterface>>; store?: Map<string, Module<any, StateInterface>>;
widget?: Widget;
requiredModules: string[]; requiredModules: string[];
version: string; version: string;
} }
@ -42,11 +45,17 @@ declare namespace FG_Plugin {
version: string; version: string;
} }
interface Widget {
widget: AsyncComponentPromise;
priority: number;
}
interface LoadedPlugins { interface LoadedPlugins {
plugins: LoadedPlugin[]; plugins: LoadedPlugin[];
routes: RouteConfig[]; routes: RouteConfig[];
mainLinks: PluginMainLink[]; mainLinks: PluginMainLink[];
shortcuts: ShortCutLink[]; shortcuts: ShortCutLink[];
shortcutsOut: ShortCutLink[]; shortcutsOut: ShortCutLink[];
widgets: Widget[];
} }
} }

View File

@ -0,0 +1,34 @@
<template>
<q-card style="text-align: center;">
<q-card-section>
<div class="text-h6">Gerücht: {{ balance.toFixed(2) }} </div>
</q-card-section>
</q-card>
</template>
<script lang="ts">
import { computed, defineComponent, onBeforeMount } from '@vue/composition-api';
import { BalanceInterface } from 'src/plugins/balance/store/balance';
import { Store } from 'vuex';
export default defineComponent({
name: 'BalanceWidget',
setup(_, { root }) {
onBeforeMount(() => {
store.dispatch('balance/getBalance').catch(err => {
console.warn(err);
});
});
const store: Store<{ balance: BalanceInterface }> = <
Store<{ balance: BalanceInterface }>
>root.$store;
const balance = computed<number>(() => {
return store.state.balance.balance;
});
return { balance };
}
});
</script>

View File

@ -11,7 +11,11 @@ const plugin: FG_Plugin.Plugin = {
version: '0.0.1', version: '0.0.1',
store: new Map<string, Module<BalanceInterface, StateInterface>>([ store: new Map<string, Module<BalanceInterface, StateInterface>>([
['balance', balance] ['balance', balance]
]) ]),
widget: {
widget: () => import('./components/Widget.vue'),
priority: 0
}
}; };
export default plugin; export default plugin;

View File

@ -0,0 +1,26 @@
<template>
<q-card class="row justify-center content-center" style="text-align: center;">
<q-card-section>
<div class="text-h6 col-12">Dienste diesen Monat: {{ jobs }}</div>
<div class="text-h6 col-12">Nächster Dienst: {{ nextJob | date }}</div>
</q-card-section>
</q-card>
</template>
<script lang="ts">
import { computed, defineComponent, onBeforeMount } from '@vue/composition-api';
import { BalanceInterface } from 'src/plugins/balance/store/balance';
import { Store } from 'vuex';
export default defineComponent({
name: 'DummyWidget',
setup(_, { root }) {
function randomNumber(start: number, end: number) {
return start + Math.floor(Math.random() * Math.floor(end));
}
const jobs = randomNumber(0, 5);
const nextJob = new Date(2021, randomNumber(1, 12), randomNumber(1, 31));
return { jobs, nextJob };
}
});
</script>

View File

@ -0,0 +1,13 @@
import { FG_Plugin } from 'src/plugins';
const plugin: FG_Plugin.Plugin = {
name: 'Schedule',
requiredModules: [],
version: '0.0.1',
widget: {
widget: () => import('./components/Widget.vue'),
priority: 0
}
};
export default plugin;

View File

@ -0,0 +1,38 @@
<template>
<q-card style="text-align: center;">
<q-card-section class="row justify-center content-stretch">
<div class="col-4">
<q-avatar style="width: 100%; height: auto;">
<img :src="avatarLink" />
</q-avatar>
</div>
<div class="col-8">
<span class="text-h6">{{ name }}</span
><br />
<span>Andere Infos wo ich aber nicht weiß was</span>
</div>
</q-card-section>
</q-card>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from '@vue/composition-api';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import MD5 from 'crypto-js/md5';
export default defineComponent({
name: 'DummyWidget',
setup(_, { root }) {
const store = <Store<StateInterface>>root.$store;
const avatarLink = computed(() => {
const hash = MD5(store.state.user.currentUser?.mail.trim());
return `https://www.gravatar.com/avatar/${hash}?s=500&d=robohash`;
});
const name = ref(store.state.user.currentUser?.display_name);
return { avatarLink, name };
}
});
</script>

View File

@ -1,37 +0,0 @@
<template>
<div>
<q-page padding class="fit row justify-center content-center items-center">
<q-card class="col-4" height="">
<q-card-section>
Name: {{ userObj.firstname }} {{ userObj.lastname }}<br />
E-Mail: {{ userObj.mail }}<br />
Roles:
<ul v-for="(role, index) in userObj.roles" :key="'role' + index">
<li>{{ role }}</li>
</ul>
<br />
Token expires: {{ sessionObj.expires | dateTime }}
</q-card-section>
</q-card>
</q-page>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
export default defineComponent({
// name: 'PageName'
setup(_, { root }) {
const store = <Store<StateInterface>>root.$store;
const userObj = computed(() => store.state.user.currentUser);
const sessionObj = computed(() => store.state.session.currentSession);
return { userObj, sessionObj };
}
});
</script>

View File

@ -13,7 +13,11 @@ const plugin: FG_Plugin.Plugin = {
store: new Map<string, Module<any, StateInterface>>([ store: new Map<string, Module<any, StateInterface>>([
['user', userStore], ['user', userStore],
['session', sessionsStore] ['session', sessionsStore]
]) ]),
widget: {
priority: 1,
widget: () => import('./components/Widget.vue')
}
}; };
export default plugin; export default plugin;

View File

@ -8,18 +8,9 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
component: () => import('../pages/MainPage.vue'), component: () => import('../pages/MainPage.vue'),
meta: { permissions: ['user'] }, meta: { permissions: ['user'] },
children: [ children: [
{
title: 'Hauptkanal',
icon: 'mdi-account-hard-hat',
path: 'user-main',
name: 'user-main',
shortcut: false,
meta: { permissions: ['user'] },
component: () => import('../pages/User.vue')
},
{ {
title: 'Einstellungen', title: 'Einstellungen',
icon: 'mdi-cog', icon: 'mdi-account-edit',
path: 'settings', path: 'settings',
name: 'user-settings', name: 'user-settings',
shortcut: true, shortcut: true,

View File

@ -20,9 +20,15 @@ const routes: RouteConfig[] = [
}, },
{ {
path: '/main', path: '/main',
redirect: 'user', redirect: 'dashboard',
component: () => import('layouts/MainLayout.vue'), component: () => import('layouts/MainLayout.vue'),
children: [ children: [
{
name: 'dashboard',
path: 'dashboard',
meta: { permission: 'user' },
component: () => import('pages/Dashboard.vue')
},
{ {
name: 'about', name: 'about',
path: 'about', path: 'about',