Merge branch 'next' of groeger-clan.duckdns.org:newgeruecht-vue into next

This commit is contained in:
Dominik 2021-01-27 22:39:21 +01:00
commit 663d1d3e4d
26 changed files with 375 additions and 290 deletions

View File

@ -24,5 +24,6 @@
"sortAttributes": false "sortAttributes": false
} }
}, },
"vetur.format.defaultFormatter.ts": "prettier-tslint" "vetur.format.defaultFormatter.ts": "prettier-tslint",
"typescript.format.enable": false
} }

View File

@ -9,6 +9,6 @@ export default defineComponent({
name: 'EmptyParent', name: 'EmptyParent',
setup() { setup() {
return {}; return {};
}, }
}); });
</script> </script>

View File

@ -49,18 +49,14 @@ export default defineComponent({
if (props.title.includes('loadFromStore')) { if (props.title.includes('loadFromStore')) {
const startIndex = props.title.indexOf('(') + 1; const startIndex = props.title.indexOf('(') + 1;
const endIndex = props.title.indexOf(')'); const endIndex = props.title.indexOf(')');
const substring = props.title const substring = props.title.substring(startIndex, endIndex).replace(/"/g, '');
.substring(startIndex, endIndex)
.replace(/"/g, '');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return <string>root.$store.getters[substring]; return <string>root.$store.getters[substring];
} }
return props.title; return props.title;
}); });
const isGranted = computed(() => const isGranted = computed(() => hasPermissions(props.permissions || [], root.$store));
hasPermissions(props.permissions || [], root.$store)
);
return { realTitle: title, isGranted }; return { realTitle: title, isGranted };
} }

View File

@ -22,9 +22,7 @@ export default defineComponent({
} }
}, },
setup(props, { root }) { setup(props, { root }) {
const isGranted = computed(() => const isGranted = computed(() => hasPermissions(props.permissions || [], root.$store));
hasPermissions(props.permissions || [], root.$store)
);
return { isGranted }; return { isGranted };
} }
}); });

View File

@ -49,18 +49,18 @@ export default defineComponent({
name: 'IsoDateInput', name: 'IsoDateInput',
props: { props: {
value: { value: {
required: true, required: true
}, },
label: {}, label: {},
readonly: { readonly: {
default: false, default: false
}, },
type: { type: {
default: 'date', default: 'date',
validator: function(value: string) { validator: function(value: string) {
return ['date', 'time', 'datetime'].indexOf(value) !== -1; return ['date', 'time', 'datetime'].indexOf(value) !== -1;
}, }
}, }
}, },
setup(props: Props, { emit }: { emit: any }) { setup(props: Props, { emit }: { emit: any }) {
function getDateTime() { function getDateTime() {
@ -183,8 +183,8 @@ export default defineComponent({
rules, rules,
timeChanged, timeChanged,
placeholder, placeholder,
dateTimeChanged, dateTimeChanged
}; };
}, }
}); });
</script> </script>

View File

@ -112,23 +112,23 @@ const links = [
name: 'about', name: 'about',
title: 'Über Flaschengeist', title: 'Über Flaschengeist',
link: 'about', link: 'about',
icon: 'mdi-information', icon: 'mdi-information'
}, }
]; ];
const shortcuts = [ const shortcuts = [
{ {
link: 'about', link: 'about',
icon: 'mdi-information', icon: 'mdi-information'
}, },
{ {
link: 'user', link: 'user',
icon: 'mdi-account', icon: 'mdi-account'
}, },
{ {
link: 'user-plugin1', link: 'user-plugin1',
icon: 'mdi-account-plus', icon: 'mdi-account-plus'
}, }
]; ];
declare module 'vue/types/vue' { declare module 'vue/types/vue' {
@ -146,7 +146,7 @@ export default defineComponent({
const leftDrawerOpen = ref( const leftDrawerOpen = ref(
computed({ computed({
get: () => (leftDrawer.value || Screen.gt.sm ? true : false), get: () => (leftDrawer.value || Screen.gt.sm ? true : false),
set: (val: boolean) => (leftDrawer.value = val), set: (val: boolean) => (leftDrawer.value = val)
}) })
); );
const leftDrawerMini = ref(false); const leftDrawerMini = ref(false);
@ -177,11 +177,9 @@ export default defineComponent({
function logout() { function logout() {
Loading.show({ message: 'Session wird abgemeldet' }); Loading.show({ message: 'Session wird abgemeldet' });
(<Store<StateInterface>>ctx.root.$store) (<Store<StateInterface>>ctx.root.$store).dispatch('session/logout').finally(() => {
.dispatch('session/logout') Loading.hide();
.finally(() => { });
Loading.hide();
});
} }
return { return {
@ -191,8 +189,8 @@ export default defineComponent({
links, links,
pluginChildLinks, pluginChildLinks,
shortcuts, shortcuts,
logout, logout
}; };
}, }
}); });
</script> </script>

View File

@ -49,6 +49,6 @@ import ShortCutLink from 'components/navigation/ShortCutLink.vue';
export default defineComponent({ export default defineComponent({
name: 'OutLayout', name: 'OutLayout',
components: { ShortCutLink }, components: { ShortCutLink }
}); });
</script> </script>

View File

@ -4,11 +4,7 @@
style="grid-auto-rows: 1fr;" style="grid-auto-rows: 1fr;"
class="fit row justify-around items-start q-col-gutter-sm" class="fit row justify-around items-start q-col-gutter-sm"
> >
<div <div v-for="(item, index) in widgets" :key="index" class="col-4 full-height col-sm-6 col-xs-12">
v-for="(item, index) in widgets"
:key="index"
class="col-4 full-height col-sm-6 col-xs-12"
>
<component v-bind:is="item" /> <component v-bind:is="item" />
</div> </div>
</q-page> </q-page>
@ -25,15 +21,14 @@ export default defineComponent({
const widgets = ref<Array<AsyncComponentPromise>>([]); const widgets = ref<Array<AsyncComponentPromise>>([]);
onMounted(() => { onMounted(() => {
root.$flaschengeistPlugins.widgets.forEach((widget) => { root.$flaschengeistPlugins.widgets.forEach(widget => {
if (hasPermissions(widget.permissions, root.$store)) if (hasPermissions(widget.permissions, root.$store)) widgets.value.push(widget.widget);
widgets.value.push(widget.widget);
}); });
}); });
return { return {
widgets, widgets
}; };
}, }
}); });
</script> </script>

View File

@ -12,14 +12,16 @@
fill="white" fill="white"
> >
<g> <g>
<circle <circle cx="87.493" cy="25.907" r="25.907" />
cx="87.493" <path
cy="25.907" d="m194.386,209.531l-42.802-36.895c-1.825-1.573-4.001-2.683-6.345-3.237l-72.533-17.136 47.755,.703v-34.439l-46.525-26.18 50.36,14.829c4.016,1.182 8.356,0.276 11.564-2.414l38.264-32.093c1.1-0.923 2.001-1.994 2.698-3.161 2.656-4.442 2.36-10.26-1.154-14.449-4.437-5.29-12.32-5.981-17.61-1.544l-33.127,27.785-45.605-13.428 41.402,1.292 16.027-13.442-6.021-2.955-3.631,3.945-18.134,2.86h-37.216c-9.665,0-17.501,7.835-17.501,17.501v90.395l.029-.024c0.252,6.569 4.819,12.433 11.527,14.019l68.966,16.292 40.024,34.501c2.834,2.442 6.318,3.639 9.787,3.639 4.213,0 8.402-1.765 11.368-5.206 5.41-6.278 4.708-15.75-1.567-21.158z"
r="25.907" />
<path
d="m233.888,50.21l-43.49-21.349c-2.17-1.065-4.545-1.612-6.94-1.612-0.861,0-1.724,0.071-2.581,0.213l-13.243,2.2-24.213-11.886c-6.197-3.043-13.688-0.485-16.728,5.713-3.042,6.197-0.484,13.687 5.713,16.729l14.402,7.069 3.539-2.969c4.405-3.694 9.994-5.729 15.738-5.729 7.266,0 14.11,3.192 18.777,8.756 6.702,7.991 7.61,19.371 2.258,28.32-0.529,0.884-1.127,1.717-1.759,2.523l28.035,13.762c0.916,0.45 1.914,0.664 2.967,0.664 5.79,0 13.263-6.495 18.05-16.247 5.66-11.524 5.424-23.236-0.525-26.157z"
/>
<path
d="m102.363,202.426l2.531,6.9-13.835,65.324c-1.716,8.105 3.463,16.065 11.567,17.782 1.048,0.222 2.092,0.328 3.122,0.328 6.936-0.001 13.165-4.839 14.66-11.896l14.265-67.357-5.513-4.752-26.797-6.329z"
/> />
<path d="m194.386,209.531l-42.802-36.895c-1.825-1.573-4.001-2.683-6.345-3.237l-72.533-17.136 47.755,.703v-34.439l-46.525-26.18 50.36,14.829c4.016,1.182 8.356,0.276 11.564-2.414l38.264-32.093c1.1-0.923 2.001-1.994 2.698-3.161 2.656-4.442 2.36-10.26-1.154-14.449-4.437-5.29-12.32-5.981-17.61-1.544l-33.127,27.785-45.605-13.428 41.402,1.292 16.027-13.442-6.021-2.955-3.631,3.945-18.134,2.86h-37.216c-9.665,0-17.501,7.835-17.501,17.501v90.395l.029-.024c0.252,6.569 4.819,12.433 11.527,14.019l68.966,16.292 40.024,34.501c2.834,2.442 6.318,3.639 9.787,3.639 4.213,0 8.402-1.765 11.368-5.206 5.41-6.278 4.708-15.75-1.567-21.158z" />
<path d="m233.888,50.21l-43.49-21.349c-2.17-1.065-4.545-1.612-6.94-1.612-0.861,0-1.724,0.071-2.581,0.213l-13.243,2.2-24.213-11.886c-6.197-3.043-13.688-0.485-16.728,5.713-3.042,6.197-0.484,13.687 5.713,16.729l14.402,7.069 3.539-2.969c4.405-3.694 9.994-5.729 15.738-5.729 7.266,0 14.11,3.192 18.777,8.756 6.702,7.991 7.61,19.371 2.258,28.32-0.529,0.884-1.127,1.717-1.759,2.523l28.035,13.762c0.916,0.45 1.914,0.664 2.967,0.664 5.79,0 13.263-6.495 18.05-16.247 5.66-11.524 5.424-23.236-0.525-26.157z" />
<path d="m102.363,202.426l2.531,6.9-13.835,65.324c-1.716,8.105 3.463,16.065 11.567,17.782 1.048,0.222 2.092,0.328 3.122,0.328 6.936-0.001 13.165-4.839 14.66-11.896l14.265-67.357-5.513-4.752-26.797-6.329z" />
</g> </g>
</svg> </svg>
</div> </div>
@ -27,7 +29,8 @@
Der Admin is über's Kabel gestolpert! Der Admin is über's Kabel gestolpert!
</div> </div>
<div> <div>
Aktuell kann der Backend Server nicht erreicht werden, wir versuchen es in {{reload}} Sekunden erneut. Aktuell kann der Backend Server nicht erreicht werden, wir versuchen es in
{{ reload }} Sekunden erneut.
</div> </div>
</div> </div>
</div> </div>
@ -49,6 +52,6 @@ export default defineComponent({
}, 1000); }, 1000);
onUnmounted(() => clearInterval(ival)); onUnmounted(() => clearInterval(ival));
return { reload }; return { reload };
}, }
}); });
</script> </script>

View File

@ -2,12 +2,26 @@
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center"> <div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div> <div>
<div> <div>
<svg style="max-width: 400px;" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 292.761 292.761" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 292.761 292.761" fill="white"> <svg
style="max-width: 400px;"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 292.761 292.761"
xmlns:xlink="http://www.w3.org/1999/xlink"
enable-background="new 0 0 292.761 292.761"
fill="white"
>
<g> <g>
<circle cx="87.493" cy="25.907" r="25.907"/> <circle cx="87.493" cy="25.907" r="25.907" />
<path d="m194.386,209.531l-42.802-36.895c-1.825-1.573-4.001-2.683-6.345-3.237l-72.533-17.136 47.755,.703v-34.439l-46.525-26.18 50.36,14.829c4.016,1.182 8.356,0.276 11.564-2.414l38.264-32.093c1.1-0.923 2.001-1.994 2.698-3.161 2.656-4.442 2.36-10.26-1.154-14.449-4.437-5.29-12.32-5.981-17.61-1.544l-33.127,27.785-45.605-13.428 41.402,1.292 16.027-13.442-6.021-2.955-3.631,3.945-18.134,2.86h-37.216c-9.665,0-17.501,7.835-17.501,17.501v90.395l.029-.024c0.252,6.569 4.819,12.433 11.527,14.019l68.966,16.292 40.024,34.501c2.834,2.442 6.318,3.639 9.787,3.639 4.213,0 8.402-1.765 11.368-5.206 5.41-6.278 4.708-15.75-1.567-21.158z"/> <path
<path d="m233.888,50.21l-43.49-21.349c-2.17-1.065-4.545-1.612-6.94-1.612-0.861,0-1.724,0.071-2.581,0.213l-13.243,2.2-24.213-11.886c-6.197-3.043-13.688-0.485-16.728,5.713-3.042,6.197-0.484,13.687 5.713,16.729l14.402,7.069 3.539-2.969c4.405-3.694 9.994-5.729 15.738-5.729 7.266,0 14.11,3.192 18.777,8.756 6.702,7.991 7.61,19.371 2.258,28.32-0.529,0.884-1.127,1.717-1.759,2.523l28.035,13.762c0.916,0.45 1.914,0.664 2.967,0.664 5.79,0 13.263-6.495 18.05-16.247 5.66-11.524 5.424-23.236-0.525-26.157z"/> d="m194.386,209.531l-42.802-36.895c-1.825-1.573-4.001-2.683-6.345-3.237l-72.533-17.136 47.755,.703v-34.439l-46.525-26.18 50.36,14.829c4.016,1.182 8.356,0.276 11.564-2.414l38.264-32.093c1.1-0.923 2.001-1.994 2.698-3.161 2.656-4.442 2.36-10.26-1.154-14.449-4.437-5.29-12.32-5.981-17.61-1.544l-33.127,27.785-45.605-13.428 41.402,1.292 16.027-13.442-6.021-2.955-3.631,3.945-18.134,2.86h-37.216c-9.665,0-17.501,7.835-17.501,17.501v90.395l.029-.024c0.252,6.569 4.819,12.433 11.527,14.019l68.966,16.292 40.024,34.501c2.834,2.442 6.318,3.639 9.787,3.639 4.213,0 8.402-1.765 11.368-5.206 5.41-6.278 4.708-15.75-1.567-21.158z"
<path d="m102.363,202.426l2.531,6.9-13.835,65.324c-1.716,8.105 3.463,16.065 11.567,17.782 1.048,0.222 2.092,0.328 3.122,0.328 6.936-0.001 13.165-4.839 14.66-11.896l14.265-67.357-5.513-4.752-26.797-6.329z"/> />
<path
d="m233.888,50.21l-43.49-21.349c-2.17-1.065-4.545-1.612-6.94-1.612-0.861,0-1.724,0.071-2.581,0.213l-13.243,2.2-24.213-11.886c-6.197-3.043-13.688-0.485-16.728,5.713-3.042,6.197-0.484,13.687 5.713,16.729l14.402,7.069 3.539-2.969c4.405-3.694 9.994-5.729 15.738-5.729 7.266,0 14.11,3.192 18.777,8.756 6.702,7.991 7.61,19.371 2.258,28.32-0.529,0.884-1.127,1.717-1.759,2.523l28.035,13.762c0.916,0.45 1.914,0.664 2.967,0.664 5.79,0 13.263-6.495 18.05-16.247 5.66-11.524 5.424-23.236-0.525-26.157z"
/>
<path
d="m102.363,202.426l2.531,6.9-13.835,65.324c-1.716,8.105 3.463,16.065 11.567,17.782 1.048,0.222 2.092,0.328 3.122,0.328 6.936-0.001 13.165-4.839 14.66-11.896l14.265-67.357-5.513-4.752-26.797-6.329z"
/>
</g> </g>
</svg> </svg>
</div> </div>
@ -15,20 +29,19 @@
Der Admin war betrunken!! Der Admin war betrunken!!
</div> </div>
<div> <div>
Einige Plugins konnten nicht geladen werden.<br />Sollte diese Seite jemals auftauchen, kontaktiere einen nüchternen Admin. Einige Plugins konnten nicht geladen werden.<br />Sollte diese Seite jemals auftauchen,
kontaktiere einen nüchternen Admin.
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent} from '@vue/composition-api'; import { defineComponent } from '@vue/composition-api';
export default defineComponent({ export default defineComponent({
name: 'PluginError.vue' name: 'PluginError.vue'
}) });
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -6,7 +6,7 @@
> >
<div class="fit row justify-center content-center items-center"> <div class="fit row justify-center content-center items-center">
<q-img <q-img
:src="$q.dark.isActive? 'logo.svg' : 'logo-dark.svg'" :src="$q.dark.isActive ? 'logo.svg' : 'logo-dark.svg'"
class="col-12 q-ma-md" class="col-12 q-ma-md"
style="min-width: 200px; max-width: 400px" style="min-width: 200px; max-width: 400px"
/> />
@ -17,16 +17,14 @@
</div> </div>
</div> </div>
<div class="col-12 text-center q-ma-sm" style="max-width: 600px;"> <div class="col-12 text-center q-ma-sm" style="max-width: 600px;">
Flaschengeist ist ein dynamischen Managementsystem für Studentenclubs. Flaschengeist ist ein dynamischen Managementsystem für Studentenclubs. Es ermöglicht unter
Es ermöglicht unter anderem die Mitgliederverwaltung, Dienstverwaltung, anderem die Mitgliederverwaltung, Dienstverwaltung, Arbeitsgruppenverwaltung und vieles
Arbeitsgruppenverwaltung und vieles meher. Es kann fast alles ermöglich meher. Es kann fast alles ermöglich werden, wenn ein Plugin dafür geschrieben wird. Jeder
werden, wenn ein Plugin dafür geschrieben wird. Jeder Club hat die Club hat die Möglichkeit sein eigenes Flaschengeist zu hosten. Ziel ist später
Möglichkeit sein eigenes Flaschengeist zu hosten. Ziel ist später
Clubübergreifend dezentralisiert miteinander zu arbeiten. Clubübergreifend dezentralisiert miteinander zu arbeiten.
</div> </div>
<q-separator />
<q-separator/>
<div class="col-12 text-h6 q-pa-sm" v-if="$route.name == 'about'"> <div class="col-12 text-h6 q-pa-sm" v-if="$route.name == 'about'">
Geladene Plugins: Geladene Plugins:
</div> </div>
@ -44,14 +42,12 @@
</q-chip> </q-chip>
</q-chip> </q-chip>
</div> </div>
<q-separator/> <q-separator />
<div class="col-12 text-h6 q-pa-sm"> <div class="col-12 text-h6 q-pa-sm">
Entwickler: Entwickler:
</div> </div>
<div <div class="fit row inline wrap justify-around items-start content-start">
class="fit row inline wrap justify-around items-start content-start"
>
<developer <developer
v-for="(developer, index) in developers" v-for="(developer, index) in developers"
:key="'dev' + index" :key="'dev' + index"
@ -69,7 +65,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {defineComponent} from '@vue/composition-api'; import { defineComponent } from '@vue/composition-api';
import Developer from 'components/about/Developer.vue'; import Developer from 'components/about/Developer.vue';
const developers = [ const developers = [
@ -105,9 +101,9 @@ const developers = [
]; ];
export default defineComponent({ export default defineComponent({
// name: 'PageName' // name: 'PageName'
components: {Developer}, components: { Developer },
setup() { setup() {
return {developers}; return { developers };
} }
}); });
</script> </script>

View File

@ -1,24 +1,33 @@
<template> <template>
<q-card> <q-card v-bind:class="{ 'bg-grey': isReversed }">
<q-card-actions align="right"> <q-card-section class="row items-start justify-between">
<q-btn <div class="col text-center">
:color="color()" <div
icon="mdi-trash-can" v-bind:class="{ 'text-negative': isNegative() }"
aria-label="Löschen" class="text-weight-bold"
:disable="disabled()" style="font-size: 2em"
@click="reverse(transaction)" >
/> <span v-if="isNegative()">-</span>{{ transaction.amount.toFixed(2) }}&#8239;
</q-card-actions> </div>
<q-card-section> <div>{{ text }}</div>
<span>{{ timeStr }}: {{ transaction.amount }}</span> <div>{{ timeStr }}</div>
</div>
<div class="col" style="text-align: right">
<q-btn
color="negative"
aria-label="Reverse transaction"
icon="mdi-trash-can"
square
:disable="!canReverse"
@click="reverse"
/>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</template> </template>
<script lang="ts"> <script lang="ts">
// TODO: Better styling import { ref, computed, defineComponent, onUnmounted, onMounted } from '@vue/composition-api';
import { ref, computed, defineComponent, onUnmounted } from '@vue/composition-api';
import { hasPermission } from 'src/utils/permission'; import { hasPermission } from 'src/utils/permission';
import { formatDateTime } from 'src/utils/datetime'; import { formatDateTime } from 'src/utils/datetime';
import { StateInterfaceBalance } from 'src/plugins/balance/store/balance'; import { StateInterfaceBalance } from 'src/plugins/balance/store/balance';
@ -30,36 +39,60 @@ interface Props {
export default defineComponent({ export default defineComponent({
name: 'Transaction', name: 'Transaction',
props: ['transaction'], props: {
transaction: {
type: Object,
required: true
}
},
watch: { transaction: 'refreshText' },
setup(props: Props, { root, emit }) { setup(props: Props, { root, emit }) {
const now = ref(Date.now()); const now = ref(Date.now());
const ival = setInterval(() => (now.value = Date.now()), 1000); const ival = setInterval(() => (now.value = Date.now()), 1000);
const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store; const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store;
const text = ref('');
onUnmounted(() => clearInterval(ival)); onUnmounted(() => clearInterval(ival));
onMounted(() => refreshText());
function canReverse(transaction: FG.Transaction) { const isNegative = () => props.transaction.sender_id === store.state.user.currentUser?.userid;
return (
hasPermission('balance_reversal', store) ||
(transaction.sender_id === store.state.user.currentUser?.userid &&
Date.now() - transaction.time.getTime() < 10000)
);
}
function color() { const refreshText = async () => {
return canReverse(props.transaction) ? 'negative' : 'grey'; if (isNegative()) {
} text.value = 'Anschreiben';
if (props.transaction.receiver_id !== null) {
const user = <FG.User>await store.dispatch('user/getUser', {
userid: props.transaction.receiver_id
});
text.value = `Gesendet an ${user.display_name}`;
}
} else {
text.value = 'Gutschrift';
if (props.transaction.sender_id !== null) {
const user = <FG.User>await store.dispatch('user/getUser', {
userid: props.transaction.sender_id
});
text.value = `Bekommen von ${user.display_name}`;
}
}
};
function disabled() { const isReversed = computed(() => props.transaction.reversal != undefined);
return !canReverse(props.transaction);
}
function reverse(transaction: FG.Transaction) { const canReverse = computed(
if (canReverse(transaction)) () =>
!isReversed.value &&
(hasPermission('balance_reversal', store) ||
(props.transaction.sender_id === store.state.user.currentUser?.userid &&
now.value - props.transaction.time.getTime() < 10000))
);
function reverse() {
if (canReverse.value)
store store
.dispatch('balance/revert', transaction) .dispatch('balance/revert', props.transaction)
.then(() => { .then(() => {
emit('reversed', transaction.id); emit('update:transaction', props.transaction);
}) })
.catch(error => console.log(error)); .catch(error => console.log(error));
} }
@ -70,7 +103,7 @@ export default defineComponent({
return formatDateTime(props.transaction.time, elapsed > 12 * 60 * 60, true, true) + ' Uhr'; return formatDateTime(props.transaction.time, elapsed > 12 * 60 * 60, true, true) + ' Uhr';
}); });
return { timeStr, disabled, color, reverse }; return { timeStr, reverse, isNegative, text, refreshText, canReverse, isReversed };
} }
}); });
</script> </script>

View File

@ -56,8 +56,9 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6"> <div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-md-4 col-sm-6">
<Transaction :transaction="transaction" @reversed="reversed" /> <!-- TODO: In Vue3 use v-model:transaction="..." -->
<Transaction :transaction.sync="transactions[index]" />
</div> </div>
</q-page> </q-page>
</template> </template>
@ -80,7 +81,7 @@ export default defineComponent({
const amount = ref<number>(0); const amount = ref<number>(0);
const showAddShortcut = ref(false); const showAddShortcut = ref(false);
const transactions = ref<FG.Transaction[]>([]); const transactions = computed(() => store.state.balance.transactions.slice().reverse());
const user = ref(store.state.user.currentUser); const user = ref(store.state.user.currentUser);
const shortCuts = ref(store.state.balance.shortcuts); const shortCuts = ref(store.state.balance.shortcuts);
@ -89,7 +90,7 @@ export default defineComponent({
); );
function addShortcut() { function addShortcut() {
void store.dispatch('balance/addShortcut', amount.value * -1); if (amount.value != 0) void store.dispatch('balance/addShortcut', amount.value * -1);
} }
function removeShortcut(shortcut: number) { function removeShortcut(shortcut: number) {
void store.dispatch('balance/removeShortcut', shortcut); void store.dispatch('balance/removeShortcut', shortcut);
@ -100,19 +101,9 @@ export default defineComponent({
function changeBalance(amount: number) { function changeBalance(amount: number) {
store store
.dispatch('balance/changeBalance', { amount: amount, user: user.value?.userid }) .dispatch('balance/changeBalance', { amount: amount, user: user.value?.userid })
.then((transaction: FG.Transaction) => {
if (transactions.value.length > 5) transactions.value.pop();
transaction.time = new Date(transaction.time);
transactions.value.unshift(transaction);
console.log(transactions.value);
})
.catch(err => console.log(err)); .catch(err => console.log(err));
} }
function reversed(id: number) {
transactions.value = transactions.value.filter(t => t.id != id);
}
return { return {
user, user,
addShortcut, addShortcut,
@ -123,7 +114,6 @@ export default defineComponent({
amount, amount,
showSelector, showSelector,
shortCuts, shortCuts,
reversed,
userUpdated userUpdated
}; };
} }

View File

@ -40,7 +40,12 @@ export default defineComponent({
align: 'left', align: 'left',
sortable: true sortable: true
}, },
{ name: 'balance', label: 'Kontostand', field: 'balance' }, {
name: 'balance',
label: 'Kontostand',
field: 'balance',
format: (val: number) => val.toFixed(2)
},
{ {
name: 'limit', name: 'limit',
label: 'Limit', label: 'Limit',

View File

@ -3,10 +3,7 @@
<q-page padding v-if="checkMain"> <q-page padding v-if="checkMain">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-list <q-list v-for="(mainRoute, index) in mainRoutes" :key="'mainRoute' + index">
v-for="(mainRoute, index) in mainRoutes"
:key="'mainRoute' + index"
>
<essential-link <essential-link
v-for="(route, index2) in mainRoute.children" v-for="(route, index2) in mainRoute.children"
:key="'route' + index2" :key="'route' + index2"

View File

@ -31,7 +31,7 @@
</q-card> </q-card>
</div> </div>
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6"> <div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6">
<Transaction :transaction="transaction" @reversed="reversed" /> <Transaction :transaction.sync="transactions[index]" />
</div> </div>
</q-page> </q-page>
</template> </template>
@ -56,7 +56,7 @@ export default defineComponent({
const sender = ref(store.state.user.currentUser); const sender = ref(store.state.user.currentUser);
const receiver = ref<FG.User | undefined>(undefined); const receiver = ref<FG.User | undefined>(undefined);
const amount = ref<number>(0); const amount = ref<number>(0);
const transactions = ref<FG.Transaction[]>([]); const transactions = computed(() => store.state.balance.transactions.slice().reverse());
const sendDisabled = computed(() => { const sendDisabled = computed(() => {
return !( return !(
@ -75,10 +75,6 @@ export default defineComponent({
receiver.value = selectedUser; receiver.value = selectedUser;
} }
function reversed(id: number) {
transactions.value = transactions.value.filter(value => value.id != id);
}
function sendAmount() { function sendAmount() {
store store
.dispatch('balance/changeBalance', { .dispatch('balance/changeBalance', {
@ -86,11 +82,6 @@ export default defineComponent({
sender: sender.value?.userid, sender: sender.value?.userid,
user: receiver.value?.userid user: receiver.value?.userid
}) })
.then((transaction: FG.Transaction) => {
if (transactions.value.length > 5) transactions.value.pop();
transaction.time = new Date(transaction.time);
transactions.value.unshift(transaction);
})
.catch(err => console.log(err)); .catch(err => console.log(err));
} }
@ -103,8 +94,7 @@ export default defineComponent({
showSelector, showSelector,
senderUpdated, senderUpdated,
receiverUpdated, receiverUpdated,
sendDisabled, sendDisabled
reversed
}; };
} }
}); });

View File

@ -16,6 +16,7 @@ export interface UserBalance extends BalanceResponse {
export interface BalanceInterface { export interface BalanceInterface {
balances: Map<string, UserBalance>; balances: Map<string, UserBalance>;
shortcuts: Array<number>; shortcuts: Array<number>;
transactions: Array<FG.Transaction>;
loading: number; loading: number;
} }
@ -26,6 +27,7 @@ export interface StateInterfaceBalance extends StateInterface {
const state: BalanceInterface = { const state: BalanceInterface = {
balances: new Map<string, UserBalance>(), balances: new Map<string, UserBalance>(),
shortcuts: [], shortcuts: [],
transactions: [],
loading: 0 loading: 0
}; };
@ -53,6 +55,15 @@ const mutations: MutationTree<BalanceInterface> = {
}, },
setShortcuts(state, data: Array<number>) { setShortcuts(state, data: Array<number>) {
state.shortcuts.splice(0, state.shortcuts.length, ...data); state.shortcuts.splice(0, state.shortcuts.length, ...data);
},
addTransaction(state, data: FG.Transaction) {
state.transactions.push(data);
},
reverseTransaction(state, data: { transaction: FG.Transaction; reversal: FG.Transaction }) {
const idx = state.transactions.findIndex(value => value.id === data.transaction.id);
data.transaction.reversal = data.reversal;
if (idx > -1) state.transactions[idx] = data.transaction;
else state.transactions.push(data.transaction);
} }
}; };
@ -111,16 +122,22 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
}) })
.finally(() => commit('setLoading', false)); .finally(() => commit('setLoading', false));
}, },
revert({ dispatch }, transaction: FG.Transaction) { revert({ dispatch, commit }, transaction: FG.Transaction) {
return axios.delete(`/balance/${transaction.id}`).then(() => { return axios
dispatch('getBalance').catch(err => console.warn(err)); .delete(`/balance/${transaction.id}`)
}); .then((response: AxiosResponse<FG.Transaction>) => {
commit('reverseTransaction', { transaction: transaction, reversal: response.data });
dispatch('getBalance').catch(err => console.warn(err));
});
}, },
changeBalance({ dispatch, commit }, data: { amount: number; user: string; sender?: string }) { changeBalance({ dispatch, commit }, data: { amount: number; user: string; sender?: string }) {
commit('setLoading'); commit('setLoading');
return axios return axios
.put(`/users/${data.user}/balance`, data) .put(`/users/${data.user}/balance`, data)
.then((response: AxiosResponse<FG.Transaction>) => { .then((response: AxiosResponse<FG.Transaction>) => {
const transaction = response.data;
transaction.time = new Date(transaction.time);
commit('addTransaction', transaction);
commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', { commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', {
userid: data.user, userid: data.user,
amount: data.amount amount: data.amount
@ -130,12 +147,12 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
userid: data.sender, userid: data.sender,
amount: -1 * data.amount amount: -1 * data.amount
}); });
return response.data; return transaction;
}) })
.catch(err => { .catch(err => {
console.debug(err);
// Maybe Balance changed // Maybe Balance changed
dispatch('getBalance').catch(err => console.warn(err)); return dispatch('getBalance', data.sender ? data.sender : data.user);
console.warn(err);
}) })
.finally(() => commit('setLoading', false)); .finally(() => commit('setLoading', false));
} }

View File

@ -5,16 +5,10 @@
Benutzereinstellungen Benutzereinstellungen
</div> </div>
<div class="col-xs-12 col-sm-6 q-pa-sm"> <div class="col-xs-12 col-sm-6 q-pa-sm">
<UserSelector <UserSelector :user="user" @update:user="userUpdated" />
:user="user"
@update:user="userUpdated"
/>
</div> </div>
</q-card-section> </q-card-section>
<MainUserSettings <MainUserSettings :user="user" @update:user="updateUser" />
:user="user"
@update:user="updateUser"
/>
</q-card> </q-card>
</template> </template>
@ -39,7 +33,7 @@ export default defineComponent({
}; };
function updateUser(value: FG.User) { function updateUser(value: FG.User) {
store.dispatch('user/updateUser', value).catch((error) => { store.dispatch('user/updateUser', value).catch(error => {
console.warn(error); console.warn(error);
}); });
} }
@ -47,9 +41,9 @@ export default defineComponent({
return { return {
user, user,
userUpdated, userUpdated,
updateUser, updateUser
}; };
}, }
}); });
</script> </script>

View File

@ -9,12 +9,16 @@
</div> </div>
</div> </div>
<div class="col-8"> <div class="col-8">
<span class="text-h6">Hallo {{ name }}</span><br /> <span class="text-h6">Hallo {{ name }}</span
><br />
<span v-if="hasBirthday">Herzlichen Glückwunsch zum Geburtstag!<br /></span> <span v-if="hasBirthday">Herzlichen Glückwunsch zum Geburtstag!<br /></span>
<span v-if="birthday.length > 0">Heute <span v-if="birthday.length === 1">hat </span><span v-else>haben </span><span <span v-if="birthday.length > 0"
v-for="(user, index) in birthday" >Heute <span v-if="birthday.length === 1">hat </span><span v-else>haben </span
v-bind:key="index" ><span v-for="(user, index) in birthday" v-bind:key="index"
>{{user.display_name}}<span v-if="index < (birthday.length-1)">, </span></span> Geburtstag.</span> >{{ user.display_name }}<span v-if="index < birthday.length - 1">, </span></span
>
Geburtstag.</span
>
<span v-else>Heute stehen keine Geburtstage an</span> <span v-else>Heute stehen keine Geburtstage an</span>
</div> </div>
</q-card-section> </q-card-section>
@ -22,12 +26,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { computed, defineComponent, onMounted, ref } from '@vue/composition-api';
computed,
defineComponent,
onMounted,
ref,
} from '@vue/composition-api';
import { Store } from 'vuex'; import { Store } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
@ -57,10 +56,10 @@ export default defineComponent({
const birthday = computed(() => const birthday = computed(() =>
store.state.user.users store.state.user.users
.filter(userHasBirthday) .filter(userHasBirthday)
.filter((user) => user.userid !== store.state.user.currentUser?.userid) .filter(user => user.userid !== store.state.user.currentUser?.userid)
); );
return { avatarLink, name, hasBirthday, birthday }; return { avatarLink, name, hasBirthday, birthday };
}, }
}); });
</script> </script>

View File

@ -2,9 +2,7 @@
<div> <div>
<q-card class="col-12"> <q-card class="col-12">
<q-form @submit="save" @reset="reset"> <q-form @submit="save" @reset="reset">
<q-card-section <q-card-section class="fit row justify-start content-center items-center">
class="fit row justify-start content-center items-center"
>
<span class="col-xs-12 col-sm-6 text-center text-h6"> <span class="col-xs-12 col-sm-6 text-center text-h6">
Rollen und Berechtigungen Rollen und Berechtigungen
</span> </span>
@ -26,17 +24,9 @@
/> />
</q-card-section> </q-card-section>
<q-separator /> <q-separator />
<q-card-section <q-card-section v-if="role" class="fit row justify-start content-center items-center">
v-if="role"
class="fit row justify-start content-center items-center"
>
<q-scroll-area style="height: 20em; width: 100%"> <q-scroll-area style="height: 20em; width: 100%">
<q-input <q-input filled v-model="newRoleName" label="neuer Name" v-if="role.id != -1" />
filled
v-model="newRoleName"
label="neuer Name"
v-if="role.id != -1"
/>
<q-option-group <q-option-group
:value="role.permissions" :value="role.permissions"
@input="updatePermissions" @input="updatePermissions"
@ -57,12 +47,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { computed, defineComponent, ref, onBeforeMount } from '@vue/composition-api';
computed,
defineComponent,
ref,
onBeforeMount,
} from '@vue/composition-api';
import { Store } from 'vuex'; import { Store } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
@ -72,10 +57,10 @@ export default defineComponent({
const store = <Store<StateInterface>>root.$store; const store = <Store<StateInterface>>root.$store;
onBeforeMount(() => { onBeforeMount(() => {
store.dispatch('user/getRoles').catch((error) => { store.dispatch('user/getRoles').catch(error => {
console.warn(error); console.warn(error);
}); });
store.dispatch('user/getPermissions').catch((error) => { store.dispatch('user/getPermissions').catch(error => {
console.warn(error); console.warn(error);
}); });
}); });
@ -83,20 +68,17 @@ export default defineComponent({
const role = ref<FG.Role | null>(null); const role = ref<FG.Role | null>(null);
const roles = computed(() => store.state.user.roles); const roles = computed(() => store.state.user.roles);
const permissions = computed(() => const permissions = computed(() =>
store.state.user.permissions.map((perm) => { store.state.user.permissions.map(perm => {
return { return {
value: perm, value: perm,
label: perm, label: perm
}; };
}) })
); );
const newRoleName = ref<string>(''); const newRoleName = ref<string>('');
function createRole( function createRole(name: string, done: (arg0: string, arg1: string) => void): void {
name: string,
done: (arg0: string, arg1: string) => void
): void {
role.value = { name: name, permissions: [], id: -1 }; role.value = { name: name, permissions: [], id: -1 };
done(name, 'add-unique'); done(name, 'add-unique');
} }
@ -116,19 +98,17 @@ export default defineComponent({
role.value = { role.value = {
id: rl.id, id: rl.id,
name: rl.name, name: rl.name,
permissions: Array.from(rl.permissions), permissions: Array.from(rl.permissions)
}; };
} }
function save() { function save() {
if (role.value) { if (role.value) {
if (role.value.id === -1) if (role.value.id === -1)
void store void store.dispatch('user/newRole', role.value).then((createdRole: FG.Role) => {
.dispatch('user/newRole', role.value) console.log(createdRole);
.then((createdRole: FG.Role) => { role.value = createdRole;
console.log(createdRole); });
role.value = createdRole;
});
else { else {
if (newRoleName.value !== '') role.value.name = newRoleName.value; if (newRoleName.value !== '') role.value.name = newRoleName.value;
console.log(role.value); console.log(role.value);
@ -139,9 +119,7 @@ export default defineComponent({
function reset() { function reset() {
if (role.value && role.value.id !== -1) { if (role.value && role.value.id !== -1) {
const original = roles.value.find( const original = roles.value.find(value => value.name === role.value?.name);
(value) => value.name === role.value?.name
);
if (original) updateRole(original); if (original) updateRole(original);
} else { } else {
role.value = null; role.value = null;
@ -156,7 +134,7 @@ export default defineComponent({
store store
.dispatch('user/deleteRole', role.value) .dispatch('user/deleteRole', role.value)
.then(() => (role.value = null)) .then(() => (role.value = null))
.catch((error) => console.warn(error)); .catch(error => console.warn(error));
} }
} }
} }
@ -172,8 +150,8 @@ export default defineComponent({
reset, reset,
removeRole, removeRole,
remove, remove,
newRoleName, newRoleName
}; };
}, }
}); });
</script> </script>

View File

@ -16,30 +16,42 @@
{{ session.platform }} {{ session.platform }}
</div> </div>
</div> </div>
<div class="row"> <div class="row" v-if="!isEdit">
<div class="col-xs-12 col-sm-6"> <div class="col-xs-12 col-sm-6">
Lebenszeit: Lebenszeit:
{{ session.lifetime }} {{ session.lifetime }}
</div> </div>
<div class="col-xs-12 col-sm-6"> <div class="col-xs-12 col-sm-6">Läuft aus: {{ session.expires | dateTime(true) }}</div>
Läuft aus: {{ session.expires | dateTime(true) }} </div>
</div> <div class="row q-my-sm" v-else>
<q-input
class="col-xs-12 col-sm-6 q-px-sm"
v-model="computedLifetime"
type="number"
label="Zeit"
filled
/>
<q-select
class="col-xs-12 col-sm-6 q-px-sm"
:options="options"
v-model="option"
filled
/>
</div> </div>
</q-card-section> </q-card-section>
<q-card-actions align="right"> <q-card-actions align="right" v-if="!isEdit">
<q-btn <q-btn flat round dense icon="mdi-pencil" @click="edit(true)" />
flat <q-btn flat round dense icon="mdi-delete" @click="deleteSession(session.token)" />
round </q-card-actions>
dense <q-card-actions align="right" v-else>
icon="mdi-delete" <q-btn flat dense label="Abbrechen" @click="edit(false)" />
@click="deleteSession(session.token)" <q-btn flat dense label="Speichern" @click="save" />
/>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from '@vue/composition-api'; import { defineComponent, ref, computed } from '@vue/composition-api';
import { Store } from 'vuex'; import { Store } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
@ -47,12 +59,14 @@ export default defineComponent({
name: 'Sessions', name: 'Sessions',
props: { props: {
session: { session: {
required: true required: true,
} },
}, },
setup(_, { root }) { setup(props: {session: FG.Session}, { root }) {
const store = <Store<StateInterface>>root.$store; const store = <Store<StateInterface>>root.$store;
const options = ref(['Minuten', 'Stunden', 'Tage']);
const option = ref<string>(options.value[0]);
const lifetime = ref(0);
function getBrowserIcon(browser: string) { function getBrowserIcon(browser: string) {
return browser == 'firefox' return browser == 'firefox'
? 'mdi-firefox' ? 'mdi-firefox'
@ -78,7 +92,7 @@ export default defineComponent({
} }
function deleteSession(token: string) { function deleteSession(token: string) {
store.dispatch('session/deleteSession', token).catch(error => { store.dispatch('session/deleteSession', token).catch((error) => {
console.warn(error); console.warn(error);
}); });
} }
@ -86,12 +100,67 @@ export default defineComponent({
return store.state.session.currentSession?.token === token; return store.state.session.currentSession?.token === token;
} }
const isEdit = ref(false);
const computedLifetime = computed({
get: () => {
switch (option.value) {
case options.value[0]:
return (lifetime.value / 60).toFixed(2);
case options.value[1]:
return (lifetime.value / (60 * 60)).toFixed(2);
case options.value[2]:
return (lifetime.value / (60 * 60 * 24)).toFixed(2);
}
},
set: (val) => {
if (val) {
switch (option.value) {
case options.value[0]:
lifetime.value = parseFloat(val) * 60;
break;
case options.value[1]:
lifetime.value = parseFloat(val) * 60 * 60;
break;
case options.value[2]:
lifetime.value = parseFloat(val) * 60 * 60 * 24;
break;
}
}
},
});
function edit(value: boolean) {
lifetime.value = props.session.lifetime;
isEdit.value = value;
}
function save() {
console.log(lifetime.value);
isEdit.value = false;
void store
.dispatch(
'session/updateSession',
{lifetime: lifetime.value, token: props.session.token}
)
.catch((error) => {
console.log(error);
});
}
return { return {
getBrowserIcon, getBrowserIcon,
getPlatformIcon, getPlatformIcon,
isThisSession, isThisSession,
deleteSession deleteSession,
isEdit,
edit,
options,
option,
lifetime,
computedLifetime,
save,
}; };
} },
}); });
</script> </script>

View File

@ -1,9 +1,6 @@
<template> <template>
<div> <div>
<q-tabs <q-tabs v-model="tab" v-if="$q.screen.gt.sm">
v-model="tab"
v-if="$q.screen.gt.sm"
>
<q-tab <q-tab
v-for="(tabindex, index) in tabs" v-for="(tabindex, index) in tabs"
:key="'tab' + index" :key="'tab' + index"
@ -11,23 +8,10 @@
:label="tabindex.label" :label="tabindex.label"
/> />
</q-tabs> </q-tabs>
<div <div class="fit row justify-end" v-else>
class="fit row justify-end" <q-btn flat round icon="mdi-menu" @click="showDrawer = !showDrawer" />
v-else
>
<q-btn
flat
round
icon="mdi-menu"
@click="showDrawer = !showDrawer"
/>
</div> </div>
<q-drawer <q-drawer side="right" v-model="showDrawer" @click="showDrawer = !showDrawer" behavior="mobile">
side="right"
v-model="showDrawer"
@click="showDrawer = !showDrawer"
behavior="mobile"
>
<q-list v-model="tab"> <q-list v-model="tab">
<q-item <q-item
v-for="(tabindex, index) in tabs" v-for="(tabindex, index) in tabs"
@ -40,10 +24,7 @@
</q-item> </q-item>
</q-list> </q-list>
</q-drawer> </q-drawer>
<q-page <q-page padding class="fit row justify-center content-start items-start q-gutter-sm">
padding
class="fit row justify-center content-start items-start q-gutter-sm"
>
<q-tab-panels <q-tab-panels
v-model="tab" v-model="tab"
style="background-color: transparent;" style="background-color: transparent;"
@ -81,9 +62,7 @@ export default defineComponent({
setup(_, { root }) { setup(_, { root }) {
const store = <Store<StateInterface>>root.$store; const store = <Store<StateInterface>>root.$store;
const canEditRoles = computed(() => const canEditRoles = computed(() => hasPermission(PERMISSIONS.ROLES_EDIT, store));
hasPermission(PERMISSIONS.ROLES_EDIT, store)
);
interface Tab { interface Tab {
name: string; name: string;
@ -93,7 +72,7 @@ export default defineComponent({
const tabs: Tab[] = [ const tabs: Tab[] = [
{ name: 'user', label: 'Mitglieder' }, { name: 'user', label: 'Mitglieder' },
{ name: 'newUser', label: 'Neues Mitglied' }, { name: 'newUser', label: 'Neues Mitglied' },
{ name: 'roles', label: 'Rollen' }, { name: 'roles', label: 'Rollen' }
]; ];
const drawer = ref<boolean>(false); const drawer = ref<boolean>(false);
@ -104,7 +83,7 @@ export default defineComponent({
}, },
set: (val: boolean) => { set: (val: boolean) => {
drawer.value = val; drawer.value = val;
}, }
}); });
const tab = ref<string>('user'); const tab = ref<string>('user');
@ -113,8 +92,8 @@ export default defineComponent({
canEditRoles, canEditRoles,
showDrawer, showDrawer,
tab, tab,
tabs, tabs
}; };
}, }
}); });
</script> </script>

View File

@ -3,10 +3,7 @@
<q-page padding v-if="checkMain"> <q-page padding v-if="checkMain">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-list <q-list v-for="(mainRoute, index) in mainRoutes" :key="'mainRoute' + index">
v-for="(mainRoute, index) in mainRoutes"
:key="'mainRoute' + index"
>
<essential-link <essential-link
v-for="(route, index2) in mainRoute.children" v-for="(route, index2) in mainRoute.children"
:key="'route' + index2" :key="'route' + index2"

View File

@ -14,12 +14,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { computed, defineComponent, onBeforeMount, ref } from '@vue/composition-api';
computed,
defineComponent,
onBeforeMount,
ref
} from '@vue/composition-api';
import Sessions from '../components/settings/Sessions.vue'; import Sessions from '../components/settings/Sessions.vue';
import MainUserSettings from '../components/settings/MainUserSettings.vue'; import MainUserSettings from '../components/settings/MainUserSettings.vue';
import { Store } from 'vuex'; import { Store } from 'vuex';
@ -40,9 +35,7 @@ export default defineComponent({
const currentUser = ref(<FG.User>store.state.user.currentUser); const currentUser = ref(<FG.User>store.state.user.currentUser);
const sessions = computed(() => store.state.session.sessions); const sessions = computed(() => store.state.session.sessions);
const loading = computed( const loading = computed(() => store.state.session.loading || store.state.user.loading > 0);
() => store.state.session.loading || store.state.user.loading > 0
);
function updateUser(value: FG.User) { function updateUser(value: FG.User) {
store.dispatch('user/updateUser', value).catch(error => { store.dispatch('user/updateUser', value).catch(error => {
console.warn(error); console.warn(error);

View File

@ -25,7 +25,7 @@ function loadCurrentSession() {
const state: SessionInterface = { const state: SessionInterface = {
sessions: [], sessions: [],
currentSession: loadCurrentSession() || undefined, currentSession: loadCurrentSession() || undefined,
loading: false loading: false,
}; };
const mutations: MutationTree<SessionInterface> = { const mutations: MutationTree<SessionInterface> = {
@ -42,7 +42,13 @@ const mutations: MutationTree<SessionInterface> = {
}, },
setLoading(state, value: boolean) { setLoading(state, value: boolean) {
state.loading = value; state.loading = value;
} },
updateSession(state, session: FG.Session) {
const index = state.sessions.findIndex((x) => x.token == session.token);
if (index > -1) {
state.sessions[index] = session;
}
},
}; };
const actions: ActionTree<SessionInterface, StateInterface> = { const actions: ActionTree<SessionInterface, StateInterface> = {
@ -59,7 +65,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
commit('setCurrentSession', response.data.session); commit('setCurrentSession', response.data.session);
commit('user/setCurrentUser', response.data.user, { root: true }); commit('user/setCurrentUser', response.data.user, { root: true });
commit('user/setCurrentPermissions', response.data.permissions, { commit('user/setCurrentPermissions', response.data.permissions, {
root: true root: true,
}); });
}) })
.catch((error: AxiosError) => { .catch((error: AxiosError) => {
@ -72,7 +78,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
*/ */
logout({ dispatch, rootState }) { logout({ dispatch, rootState }) {
if (rootState.session.currentSession) { if (rootState.session.currentSession) {
dispatch('deleteSession', rootState.session.currentSession.token).catch(error => { dispatch('deleteSession', rootState.session.currentSession.token).catch((error) => {
console.log(error); console.log(error);
void dispatch('clearCurrent', false); void dispatch('clearCurrent', false);
}); });
@ -91,7 +97,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
if (token === rootState.session.currentSession?.token) { if (token === rootState.session.currentSession?.token) {
void dispatch('clearCurrent', false); void dispatch('clearCurrent', false);
} else { } else {
dispatch('getSessions').catch(error => { dispatch('getSessions').catch((error) => {
throw error; throw error;
}); });
} }
@ -110,7 +116,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
void Router.push({ void Router.push({
name: 'login', name: 'login',
query: redirect ? { redirect: Router.currentRoute.fullPath } : {}, query: redirect ? { redirect: Router.currentRoute.fullPath } : {},
params: { logout: 'true' } params: { logout: 'true' },
}).then(() => { }).then(() => {
commit('clearCurrentSession'); commit('clearCurrentSession');
commit('user/clearCurrentUser', null, { root: true }); commit('user/clearCurrentUser', null, { root: true });
@ -126,7 +132,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
axios axios
.get('/auth') .get('/auth')
.then((response: AxiosResponse<FG.Session[]>) => { .then((response: AxiosResponse<FG.Session[]>) => {
response.data.forEach(session => { response.data.forEach((session) => {
session.expires = new Date(session.expires); session.expires = new Date(session.expires);
}); });
commit('setSessions', response.data); commit('setSessions', response.data);
@ -137,13 +143,29 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
commit('setCurrentSession', currentSession); commit('setCurrentSession', currentSession);
} }
}) })
.catch(error => { .catch((error) => {
throw error; throw error;
}) })
.finally(() => { .finally(() => {
commit('setLoading', false); commit('setLoading', false);
}); });
}, },
updateSession({ commit, state }, data: { lifetime: number; token: string }) {
commit('setLoading', true);
axios
.put(`auth/${data.token}`, { value: data.lifetime })
.then((response: AxiosResponse<FG.Session>) => {
response.data.expires = new Date(response.data.expires);
if (state.currentSession?.token == response.data.token) {
commit('setCurrentSession', response.data);
}
})
.catch((err) => console.log(err))
.finally(() => {
commit('setLoading', false);
});
console.log('updateSession', data);
},
requestPasswordReset({}, data) { requestPasswordReset({}, data) {
return axios.post('/auth/reset', data); return axios.post('/auth/reset', data);
}, },
@ -151,7 +173,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
return axios.post('/auth/reset', data).catch((error: AxiosError) => { return axios.post('/auth/reset', data).catch((error: AxiosError) => {
return Promise.reject(error.response); return Promise.reject(error.response);
}); });
} },
}; };
const getters: GetterTree<SessionInterface, StateInterface> = { const getters: GetterTree<SessionInterface, StateInterface> = {
@ -163,7 +185,7 @@ const getters: GetterTree<SessionInterface, StateInterface> = {
}, },
loading(state) { loading(state) {
return state.loading; return state.loading;
} },
}; };
const sessions: Module<SessionInterface, StateInterface> = { const sessions: Module<SessionInterface, StateInterface> = {
@ -171,7 +193,7 @@ const sessions: Module<SessionInterface, StateInterface> = {
state, state,
mutations, mutations,
actions, actions,
getters getters,
}; };
export default sessions; export default sessions;

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'; import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { StateInterface } from 'src/store'; import { StateInterface } from 'src/store';
import { axios } from 'boot/axios'; import { axios } from 'boot/axios';
@ -48,6 +49,11 @@ const mutations: MutationTree<UserStateInterface> = {
setUsers(state, data: FG.User[]) { setUsers(state, data: FG.User[]) {
state.users = data; state.users = data;
}, },
setUser(state, data: FG.User) {
const index = state.users.findIndex(x => x.userid === data.userid);
if (index > -1) state.users[index] = data;
else state.users.push(data);
},
setRoles(state, data: FG.Role[]) { setRoles(state, data: FG.Role[]) {
state.roles = data; state.roles = data;
}, },
@ -221,10 +227,26 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
commit('setPermissions', response.data); commit('setPermissions', response.data);
}) })
.finally(() => commit('setLoading', false)); .finally(() => commit('setLoading', false));
},
getUser({ commit, getters }, data: { userid: string; force?: boolean }) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const user = <FG.User | undefined>getters['getUser'](data.userid);
if (user === undefined || data.force === true) {
return axios.get(`/users/${data.userid}`).then((response: AxiosResponse<FG.User>) => {
commit('setUser', response.data);
return response.data;
});
} else {
return Promise.resolve(user);
}
} }
}; };
const getters: GetterTree<UserStateInterface, StateInterface> = { const getters: GetterTree<UserStateInterface, StateInterface> = {
getUser: state => (userid: string) => {
const user = state.users.filter(usr => usr.userid === userid);
return user.length > 0 ? user[0] : undefined;
},
currentUser({ currentUser }) { currentUser({ currentUser }) {
return currentUser; return currentUser;
}, },