Merged balance actions into one page

This commit is contained in:
Ferdinand Thiessen 2021-01-29 04:37:56 +01:00
parent 17e640892a
commit 502c40329c
6 changed files with 205 additions and 190 deletions

View File

@ -1,67 +1,59 @@
<template> <template>
<q-page padding class="fit row justify-left q-col-gutter-sm"> <q-card>
<div class="col-12"> <BalanceHeader @update:user="userUpdated" :showSelector="showSelector" />
<q-card> <q-separator />
<BalanceHeader @update:user="userUpdated" :showSelector="showSelector" />
<q-separator />
<q-card-section class="row q-col-gutter-md" v-if="shortCuts"> <q-card-section class="row q-col-gutter-md" v-if="shortCuts">
<div :key="index" v-for="(shortcut, index) in shortCuts" class="col-4"> <div :key="index" v-for="(shortcut, index) in shortCuts" class="col-4">
<q-btn <q-btn
push push
v-if="shortcut" v-if="shortcut"
color="primary" color="primary"
style="width: 100%" style="width: 100%"
:label="shortcut.toFixed(2).toString() + ' €'" :label="shortcut.toFixed(2).toString() + ' €'"
@click="changeBalance(shortcut)" @click="changeBalance(shortcut)"
>
<q-popup-proxy context-menu>
<q-btn label="Entfernen" @click="removeShortcut(shortcut)" />
</q-popup-proxy>
<q-tooltip>Rechtsklick um Verknüpfung zu entfernen</q-tooltip>
</q-btn>
</div></q-card-section
> >
<q-card-section class="row q-col-gutter-md items-center"> <q-popup-proxy context-menu>
<div class="col-sm-4 col-xs-12"> <q-btn label="Entfernen" @click="removeShortcut(shortcut)" />
<q-input </q-popup-proxy>
v-model.number="amount" <q-tooltip>Rechtsklick um Verknüpfung zu entfernen</q-tooltip>
type="number" </q-btn>
filled </div></q-card-section
label="Eigener Betrag" >
step="0.1" <q-card-section class="row q-col-gutter-md items-center">
min="0" <div class="col-sm-4 col-xs-12">
/> <q-input
</div> v-model.number="amount"
<div class="col-sm-4 col-xs-6"> type="number"
<q-btn filled
style="width: 100%" label="Eigener Betrag"
color="primary" step="0.1"
label="Anschreiben" min="0"
@click="changeBalance(amount * -1)" />
><q-tooltip>Rechtsklick um Betrag als Verknüpfung hinzuzufügen</q-tooltip> </div>
<q-popup-proxy context-menu v-model="showAddShortcut"> <div class="col-sm-4 col-xs-6">
<q-btn label="neue Verknüpfung" @click="addShortcut"></q-btn> <q-btn
</q-popup-proxy> style="width: 100%"
</q-btn> color="primary"
</div> label="Anschreiben"
<div class="col-sm-4 col-xs-6"> @click="changeBalance(amount * -1)"
<q-btn ><q-tooltip>Rechtsklick um Betrag als Verknüpfung hinzuzufügen</q-tooltip>
v-if="canAddCredit" <q-popup-proxy context-menu v-model="showAddShortcut">
style="width: 100%" <q-btn label="neue Verknüpfung" @click="addShortcut"></q-btn>
color="secondary" </q-popup-proxy>
label="Gutschreiben" </q-btn>
@click="changeBalance(amount)" </div>
/> <div class="col-sm-4 col-xs-6">
</div> <q-btn
</q-card-section> v-if="canAddCredit"
</q-card> style="width: 100%"
</div> color="secondary"
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-md-4 col-sm-6"> label="Gutschreiben"
<!-- TODO: In Vue3 use v-model:transaction="..." --> @click="changeBalance(amount)"
<Transaction :transaction.sync="transactions[index]" /> />
</div> </div>
</q-page> </q-card-section>
</q-card>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -69,20 +61,25 @@ import { computed, ref, defineComponent, onBeforeMount } from '@vue/composition-
import { hasPermission } from 'src/utils/permission'; import { hasPermission } from 'src/utils/permission';
import { StateInterfaceBalance } from '../store/balance'; import { StateInterfaceBalance } from '../store/balance';
import { Store } from 'vuex'; import { Store } from 'vuex';
import Transaction from '../components/Transaction.vue';
import BalanceHeader from '../components/BalanceHeader.vue'; import BalanceHeader from '../components/BalanceHeader.vue';
import PERMISSIONS from '../permissions'; import PERMISSIONS from '../permissions';
export default defineComponent({ export default defineComponent({
name: 'BalanceAdd', name: 'BalanceAdd',
components: { Transaction, BalanceHeader }, components: { BalanceHeader },
setup(_, { root }) { setup(_, { root }) {
onBeforeMount(() => void store.dispatch('balance/getShortcuts')); onBeforeMount(() => {
void store.dispatch('balance/getShortcuts');
if (store.state.balance.transactions.length == 0)
// No transaction, load at most six since yesterday
void store.dispatch('balance/getTransactions', {
filter: { limit: 6, from: new Date(new Date().setDate(new Date().getDate() - 1)) }
});
});
const store = <Store<StateInterfaceBalance>>root.$store; const store = <Store<StateInterfaceBalance>>root.$store;
const amount = ref<number>(0); const amount = ref<number>(0);
const showAddShortcut = ref(false); const showAddShortcut = ref(false);
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);
@ -113,7 +110,6 @@ export default defineComponent({
removeShortcut, removeShortcut,
showAddShortcut, showAddShortcut,
changeBalance, changeBalance,
transactions,
amount, amount,
showSelector, showSelector,
shortCuts, shortCuts,

View File

@ -1,39 +1,25 @@
<template> <template>
<q-page padding class="fit row justify-left q-col-gutter-sm"> <q-card>
<div class="col-12"> <BalanceHeader @update:user="senderUpdated" :showSelector="showSelector" />
<q-card> <q-separator />
<BalanceHeader @update:user="senderUpdated" :showSelector="showSelector" /> <q-card-section class="row q-col-gutter-md items-center">
<q-separator /> <div class="col-sm-4 col-xs-12">
<q-card-section class="row q-col-gutter-md items-center"> <q-input v-model.number="amount" type="number" filled label="Betrag" step="0.1" min="0" />
<div class="col-sm-4 col-xs-12"> </div>
<q-input <div class="col-sm-4 col-xs-6">
v-model.number="amount" <UserSelector :user="receiver" @update:user="receiverUpdated" label="Empfänger" />
type="number" </div>
filled <div class="col-sm-4 col-xs-6">
label="Betrag" <q-btn
step="0.1" style="width: 100%"
min="0" color="primary"
/> :disable="sendDisabled"
</div> label="Senden"
<div class="col-sm-4 col-xs-6"> @click="sendAmount"
<UserSelector :user="receiver" @update:user="receiverUpdated" label="Empfänger" /> />
</div> </div>
<div class="col-sm-4 col-xs-6"> </q-card-section>
<q-btn </q-card>
style="width: 100%"
color="primary"
:disable="sendDisabled"
label="Senden"
@click="sendAmount"
/>
</div>
</q-card-section>
</q-card>
</div>
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6">
<Transaction :transaction.sync="transactions[index]" />
</div>
</q-page>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -42,13 +28,12 @@ import { hasPermission } from 'src/utils/permission';
import { StateInterfaceBalance } from '../store/balance'; import { StateInterfaceBalance } from '../store/balance';
import { Store } from 'vuex'; import { Store } from 'vuex';
import UserSelector from 'src/plugins/user/components/UserSelector.vue'; import UserSelector from 'src/plugins/user/components/UserSelector.vue';
import Transaction from '../components/Transaction.vue';
import BalanceHeader from '../components/BalanceHeader.vue'; import BalanceHeader from '../components/BalanceHeader.vue';
import PERMISSIONS from '../permissions'; import PERMISSIONS from '../permissions';
export default defineComponent({ export default defineComponent({
name: 'BalanceTransfer', name: 'BalanceTransfer',
components: { Transaction, BalanceHeader, UserSelector }, components: { BalanceHeader, UserSelector },
setup(_, { root }) { setup(_, { root }) {
const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store; const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store;
@ -56,7 +41,6 @@ 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 = computed(() => store.state.balance.transactions.slice().reverse());
const sendDisabled = computed(() => { const sendDisabled = computed(() => {
return !( return !(
@ -90,7 +74,6 @@ export default defineComponent({
receiver, receiver,
amount, amount,
sendAmount, sendAmount,
transactions,
showSelector, showSelector,
senderUpdated, senderUpdated,
receiverUpdated, receiverUpdated,

View File

@ -1,48 +1,112 @@
<template> <template>
<div> <div>
<q-page padding v-if="checkMain"> <q-tabs v-model="tab" v-if="$q.screen.gt.sm">
<q-card> <q-tab
<q-card-section> v-for="(tabindex, index) in tabs"
<q-list v-for="(mainRoute, index) in mainRoutes" :key="'mainRoute' + index"> :key="'tab' + index"
<essential-link :name="tabindex.name"
v-for="(route, index2) in mainRoute.children" :label="tabindex.label"
:key="'route' + index2" />
:title="route.title" </q-tabs>
:icon="route.icon" <div class="fit row justify-end" v-else>
:link="route.name" <q-btn flat round icon="mdi-menu" @click="showDrawer = !showDrawer" />
:permissions="route.meta.permissions" </div>
/> <q-drawer side="right" v-model="showDrawer" @click="showDrawer = !showDrawer" behavior="mobile">
</q-list> <q-list v-model="tab">
</q-card-section> <q-item
</q-card> v-for="(tabindex, index) in tabs"
:key="'tab' + index"
:active="tab == tabindex.name"
clickable
@click="tab = tabindex.name"
>
<q-item-label>{{ tabindex.label }}</q-item-label>
</q-item>
</q-list>
</q-drawer>
<q-page padding class="fit row justify-left q-col-gutter-sm">
<q-tab-panels
v-model="tab"
style="background-color: transparent"
class="q-pa-none col-12"
animated
>
<q-tab-panel name="add" class="q-px-xs">
<BalanceAdd />
</q-tab-panel>
<q-tab-panel name="transfer" class="q-px-xs">
<BalanceTransfer />
</q-tab-panel>
</q-tab-panels>
<div
v-for="(transaction, index) in transactions"
v-bind:key="index"
class="col-md-4 col-sm-6"
>
<!-- TODO: In Vue3 use v-model:transaction="..." -->
<Transaction :transaction.sync="transactions[index]" />
</div>
</q-page> </q-page>
<router-view />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent } from '@vue/composition-api'; import { computed, defineComponent, ref } from '@vue/composition-api';
import EssentialLink from 'src/components/navigation/EssentialLink.vue';
import mainRoutes from 'src/plugins/balance/routes';
import { Store } from 'vuex'; import { Store } from 'vuex';
import { BalanceInterface } from 'src/plugins/balance/store/balance'; import { hasPermissions, hasSomePermissions } from 'src/utils/permission';
import setLoadingBar from 'src/utils/loading'; import PERMISSIONS from '../permissions';
import { StateInterface } from 'src/store'; import { Screen } from 'quasar';
import BalanceAdd from '../components/BalanceAdd.vue';
import BalanceTransfer from '../components/BalanceTransfer.vue';
import Transaction from '../components/Transaction.vue';
import { StateInterfaceBalance } from '../store/balance';
export default defineComponent({ export default defineComponent({
// name: 'PageName' name: 'BalanceManage',
components: { EssentialLink }, components: { BalanceAdd, BalanceTransfer, Transaction },
setup(_, { root }) { setup(_, { root }) {
const store = <Store<StateInterface>>root.$store; const store = <Store<StateInterfaceBalance>>root.$store;
const loading = computed(() => {
return (<BalanceInterface>store.state.balance).loading > 0; const transactions = computed(() =>
}); store.state.balance.transactions
const checkMain = computed(() => { .filter(t => t.original_id == undefined)
return root.$route.matched.length == 2; .sort((a, b) => (a.time >= b.time ? -1 : 1))
);
const canAdd = () =>
hasSomePermissions([PERMISSIONS.DEBIT, PERMISSIONS.CREDIT, PERMISSIONS.DEBIT_OWN], store);
interface Tab {
name: string;
label: string;
}
const tabs: Tab[] = [
...(canAdd() ? [{ name: 'add', label: 'Anschreiben' }] : []),
...(hasSomePermissions([PERMISSIONS.SEND, PERMISSIONS.SEND_OTHER], store)
? [{ name: 'transfer', label: 'Übertragen' }]
: [])
];
const drawer = ref<boolean>(false);
const showDrawer = computed({
get: () => {
return !Screen.gt.sm && drawer.value;
},
set: (val: boolean) => {
drawer.value = val;
}
}); });
setLoadingBar(loading); const tab = ref<string>(canAdd() ? 'add' : 'transfer');
return { checkMain, mainRoutes }; return {
showDrawer,
tab,
tabs,
transactions
};
} }
}); });
</script> </script>

View File

@ -7,32 +7,32 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
icon: 'mdi-cash-100', icon: 'mdi-cash-100',
path: 'balance', path: 'balance',
name: 'balance', name: 'balance',
redirect: { name: 'balance-add' }, redirect: { name: 'balance-view' },
meta: { permissions: ['user'] }, meta: { permissions: ['user'] },
children: [ children: [
{ {
title: 'Anschreiben', title: 'Übersicht',
icon: 'mdi-cash-plus', icon: 'mdi-cash-plus',
path: 'add', path: 'overview',
name: 'balance-add', name: 'balance-view',
shortcut: true, meta: { permissions: [permissions.SHOW] },
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] }, component: () => import('../pages/Overview.vue')
component: () => import('../pages/Add.vue')
}, },
{ {
title: 'Übertragen', title: 'Buchen',
icon: 'mdi-cash-refund', icon: 'mdi-cash-plus',
path: 'transfer', path: 'change',
name: 'balance-transfer', name: 'balance-change',
meta: { permissions: [permissions.SEND] }, shortcut: true,
component: () => import('../pages/Transfer.vue') meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
component: () => import('../pages/MainPage.vue')
}, },
{ {
title: 'Verwaltung', title: 'Verwaltung',
icon: 'mdi-account-cash', icon: 'mdi-account-cash',
path: 'admin', path: 'admin',
name: 'balance-admin', name: 'balance-admin',
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] }, meta: { permissions: [permissions.SET_LIMIT, permissions.SHOW_OTHER] },
component: () => import('../pages/Admin.vue') component: () => import('../pages/Admin.vue')
} }
] ]

View File

@ -1,25 +1,5 @@
import { FG_Plugin } from 'src/plugins'; import { FG_Plugin } from 'src/plugins';
/*const permissions = {
// Show own and others balance
SHOW: 'balance_show',
SHOW_OTHER: 'balance_show_others',
// Credit balance (give)
CREDIT: 'balance_credit',
// Debit balance (take)
DEBIT: 'balance_debit',
// Debit own balance only
DEBIT_OWN: 'balance_debit_own',
// Send from to other
SEND: 'balance_send',
// Send from other to another
SEND_OTHER: 'balance_send_others',
// Can set limit for users
SET_LIMIT: 'balance_set_limit',
//Allow sending / sub while exceeding the set limit
EXCEED_LIMIT: 'balance_exceed_limit'
};*/
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
{ {
title: 'Dienste', title: 'Dienste',

View File

@ -31,12 +31,7 @@
label="Zeit" label="Zeit"
filled filled
/> />
<q-select <q-select class="col-xs-12 col-sm-6 q-px-sm" :options="options" v-model="option" filled />
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" v-if="!isEdit"> <q-card-actions align="right" v-if="!isEdit">
@ -59,10 +54,10 @@ export default defineComponent({
name: 'Sessions', name: 'Sessions',
props: { props: {
session: { session: {
required: true, required: true
}, }
}, },
setup(props: {session: FG.Session}, { 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 options = ref(['Minuten', 'Stunden', 'Tage']);
const option = ref<string>(options.value[0]); const option = ref<string>(options.value[0]);
@ -92,7 +87,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);
}); });
} }
@ -113,7 +108,7 @@ export default defineComponent({
return (lifetime.value / (60 * 60 * 24)).toFixed(2); return (lifetime.value / (60 * 60 * 24)).toFixed(2);
} }
}, },
set: (val) => { set: val => {
if (val) { if (val) {
switch (option.value) { switch (option.value) {
case options.value[0]: case options.value[0]:
@ -127,7 +122,7 @@ export default defineComponent({
break; break;
} }
} }
}, }
}); });
function edit(value: boolean) { function edit(value: boolean) {
@ -139,11 +134,8 @@ export default defineComponent({
console.log(lifetime.value); console.log(lifetime.value);
isEdit.value = false; isEdit.value = false;
void store void store
.dispatch( .dispatch('session/updateSession', { lifetime: lifetime.value, token: props.session.token })
'session/updateSession', .catch(error => {
{lifetime: lifetime.value, token: props.session.token}
)
.catch((error) => {
console.log(error); console.log(error);
}); });
} }
@ -159,8 +151,8 @@ export default defineComponent({
option, option,
lifetime, lifetime,
computedLifetime, computedLifetime,
save, save
}; };
}, }
}); });
</script> </script>