Add transfers

This commit is contained in:
Knut Ahlers 2024-01-17 23:17:21 +01:00
parent 5779e9e8a9
commit cfc9a677af
Signed by: luzifer
SSH Key Fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
2 changed files with 208 additions and 48 deletions

View File

@ -3,7 +3,6 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<!-- TODO: Add time-selector -->
<range-selector <range-selector
v-model="timeRange" v-model="timeRange"
/> />
@ -15,7 +14,7 @@
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button <button
class="btn" class="btn"
@click="showAddTransaction = true" @click="showAddTransaction = !showAddTransaction"
> >
<i class="fas fa-fw fa-plus-circle mr-1" /> <i class="fas fa-fw fa-plus-circle mr-1" />
Add Transaction Add Transaction

View File

@ -1,60 +1,175 @@
<template> <template>
<div class="container-fluid"> <div>
<div class="row"> <div class="container-fluid">
<div class="col d-flex align-items-center"> <div class="row">
<range-selector <div class="col d-flex align-items-center">
v-model="timeRange" <range-selector
:multi-month="false" v-model="timeRange"
/> :multi-month="false"
/>
</div>
<div class="col d-flex align-items-center justify-content-center">
<div :class="unallocatedMoneyClass">
<span class="fs-4">{{ formatNumber(unallocatedMoney) }} </span>
<span class="small">Unallocated</span>
</div>
</div>
<div class="col d-flex align-items-center justify-content-end">
<div class="btn-group btn-group-sm">
<button
class="btn"
data-bs-toggle="modal"
data-bs-target="#transferMoneyModal"
>
<i class="fas fa-fw fa-arrow-right-arrow-left mr-1" />
Add Transfer
</button>
</div>
</div>
</div> </div>
<div class="col d-flex align-items-center justify-content-end"> <div class="row mt-3">
<div :class="unallocatedMoneyClass"> <div class="col">
<span class="fs-4">{{ formatNumber(unallocatedMoney) }} </span> <table class="table table-striped small">
<span class="small">Unallocated</span> <thead>
<tr>
<th>Category</th>
<th class="text-end">
Allocated
</th>
<th class="text-end">
Activity
</th>
<th class="text-end">
Available
</th>
</tr>
</thead>
<tbody>
<tr
v-for="cat in categories"
:key="cat.id"
>
<td>{{ cat.name }}</td>
<td :class="{'text-end': true, 'text-danger': (allocatedByCategory[cat.id] || 0) < 0}">
{{ formatNumber(allocatedByCategory[cat.id] || 0) }}
</td>
<td :class="{'text-end': true, 'text-danger': (activityByCategory[cat.id] || 0) < 0}">
{{ formatNumber(activityByCategory[cat.id] || 0) }}
</td>
<td :class="{'text-end': true, 'text-danger': cat.balance < 0}">
{{ formatNumber(cat.balance) }}
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-3">
<div class="col"> <div
<table class="table table-striped small"> id="transferMoneyModal"
<thead> ref="transferMoneyModal"
<tr> class="modal fade"
<th>Category</th> tabindex="-1"
<th class="text-end"> aria-labelledby="transferMoneyModalLabel"
Allocated aria-hidden="true"
</th> >
<th class="text-end"> <div class="modal-dialog modal-sm">
Activity <div class="modal-content">
</th> <div class="modal-header">
<th class="text-end"> <h1
Available id="transferMoneyModalLabel"
</th> class="modal-title fs-5"
</tr>
</thead>
<tbody>
<tr
v-for="cat in categories"
:key="cat.id"
> >
<td>{{ cat.name }}</td> Transfer Money
<td :class="{'text-end': true, 'text-danger': (allocatedByCategory[cat.id] || 0) < 0}"> </h1>
{{ formatNumber(allocatedByCategory[cat.id] || 0) }} <button
</td> type="button"
<td :class="{'text-end': true, 'text-danger': (activityByCategory[cat.id] || 0) < 0}"> class="btn-close"
{{ formatNumber(activityByCategory[cat.id] || 0) }} data-bs-dismiss="modal"
</td> aria-label="Close"
<td :class="{'text-end': true, 'text-danger': cat.balance < 0}"> />
{{ formatNumber(cat.balance) }} </div>
</td> <div class="modal-body">
</tr> <div class="mb-3">
</tbody> <label
</table> for="transferMoneyModalFrom"
class="form-label"
>From</label>
<select
id="transferMoneyModalFrom"
v-model="modals.createTransfer.from"
class="form-select"
>
<option
v-for="cat in transferableCategories"
:key="cat.id"
:value="cat.id"
>
{{ cat.name }}
</option>
</select>
</div>
<div class="mb-3">
<label
for="transferMoneyModalTo"
class="form-label"
>To</label>
<select
id="transferMoneyModalTo"
v-model="modals.createTransfer.to"
class="form-select"
>
<option
v-for="cat in transferableCategories"
:key="cat.id"
:value="cat.id"
>
{{ cat.name }}
</option>
</select>
</div>
<div class="mb-3">
<label
for="transferMoneyModalAmount"
class="form-label"
>Amount</label>
<div class="input-group">
<input
id="transferMoneyModalAmount"
v-model.number="modals.createTransfer.amount"
type="number"
min="0.01"
step="0.01"
class="form-control text-end"
>
<span class="input-group-text"></span>
</div>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
:disabled="!transferModalValid"
@click="transferMoney"
>
<i class="fas fa-fw fa-arrow-right-arrow-left mr-1" />
Transfer
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
/* eslint-disable sort-imports */
import { Modal } from 'bootstrap'
import { formatNumber } from '../helpers' import { formatNumber } from '../helpers'
import rangeSelector from './rangeSelector.vue' import rangeSelector from './rangeSelector.vue'
import { unallocatedMoneyAcc } from '../constants' import { unallocatedMoneyAcc } from '../constants'
@ -89,6 +204,25 @@ export default {
return accounts return accounts
}, },
transferModalValid() {
if (!this.modals.createTransfer.from || !this.modals.createTransfer.to) {
return false
}
if (this.modals.createTransfer.from === this.modals.createTransfer.to) {
return false
}
return true
},
transferableCategories() {
const accounts = this.accounts
.filter(acc => acc.type === 'category')
accounts.sort((a, b) => a.name.localeCompare(b.name))
return accounts
},
unallocatedMoney() { unallocatedMoney() {
const acc = this.accounts.filter(acc => acc.id === unallocatedMoneyAcc)[0] || null const acc = this.accounts.filter(acc => acc.id === unallocatedMoneyAcc)[0] || null
if (acc === null) { if (acc === null) {
@ -98,7 +232,7 @@ export default {
}, },
unallocatedMoneyClass() { unallocatedMoneyClass() {
const classes = ['d-inline-flex', 'flex-column', 'text-center', 'ms-auto', 'p-2', 'rounded'] const classes = ['d-inline-flex', 'flex-column', 'text-center', 'p-2', 'rounded']
if (this.unallocatedMoney < 0) { if (this.unallocatedMoney < 0) {
classes.push('bg-danger') classes.push('bg-danger')
} else if (this.unallocatedMoney === 0) { } else if (this.unallocatedMoney === 0) {
@ -113,6 +247,14 @@ export default {
data() { data() {
return { return {
modals: {
createTransfer: {
amount: 0,
from: unallocatedMoneyAcc,
to: '',
},
},
timeRange: {}, timeRange: {},
transactions: [], transactions: [],
} }
@ -131,6 +273,25 @@ export default {
}, },
formatNumber, formatNumber,
transferMoney() {
const params = new URLSearchParams()
params.set('amount', this.modals.createTransfer.amount.toFixed(2))
return fetch(`/api/accounts/${this.modals.createTransfer.from}/transfer/${this.modals.createTransfer.to}?${params.toString()}`, {
method: 'PUT',
})
.then(() => {
this.$emit('update-accounts')
Modal.getInstance(this.$refs.transferMoneyModal).toggle()
this.modals.createTransfer = {
amount: 0,
from: unallocatedMoneyAcc,
to: '',
}
})
},
}, },
name: 'AccountingAppBudgetDashboard', name: 'AccountingAppBudgetDashboard',