2024-01-16 16:30:17 +00:00
|
|
|
<template>
|
|
|
|
<div class="container-fluid">
|
|
|
|
<div class="row">
|
2024-01-17 22:02:41 +00:00
|
|
|
<div class="col d-flex align-items-center">
|
|
|
|
<range-selector
|
|
|
|
v-model="timeRange"
|
|
|
|
:multi-month="false"
|
|
|
|
/>
|
2024-01-16 16:30:17 +00:00
|
|
|
</div>
|
|
|
|
<div class="col d-flex align-items-center justify-content-end">
|
|
|
|
<div :class="unallocatedMoneyClass">
|
2024-01-17 22:02:41 +00:00
|
|
|
<span class="fs-4">{{ formatNumber(unallocatedMoney) }} €</span>
|
2024-01-16 16:30:17 +00:00
|
|
|
<span class="small">Unallocated</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
|
|
<div class="col">
|
|
|
|
<table class="table table-striped small">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>Category</th>
|
2024-01-17 22:02:41 +00:00
|
|
|
<th class="text-end">
|
|
|
|
Allocated
|
|
|
|
</th>
|
2024-01-16 16:30:17 +00:00
|
|
|
<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>
|
2024-01-17 22:02:41 +00:00
|
|
|
<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>
|
2024-01-16 16:30:17 +00:00
|
|
|
<td :class="{'text-end': true, 'text-danger': cat.balance < 0}">
|
2024-01-17 22:02:41 +00:00
|
|
|
{{ formatNumber(cat.balance) }} €
|
2024-01-16 16:30:17 +00:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2024-01-17 22:02:41 +00:00
|
|
|
import { formatNumber } from '../helpers'
|
|
|
|
import rangeSelector from './rangeSelector.vue'
|
2024-01-16 16:30:17 +00:00
|
|
|
import { unallocatedMoneyAcc } from '../constants'
|
|
|
|
|
|
|
|
export default {
|
2024-01-17 22:02:41 +00:00
|
|
|
components: { rangeSelector },
|
|
|
|
|
2024-01-16 16:30:17 +00:00
|
|
|
computed: {
|
2024-01-17 22:02:41 +00:00
|
|
|
activityByCategory() {
|
|
|
|
return this.transactions
|
|
|
|
.filter(tx => tx.account)
|
|
|
|
.reduce((alloc, tx) => {
|
|
|
|
alloc[tx.category] = (alloc[tx.category] || 0) + tx.amount
|
|
|
|
return alloc
|
|
|
|
}, {})
|
|
|
|
},
|
|
|
|
|
|
|
|
allocatedByCategory() {
|
|
|
|
return this.transactions
|
|
|
|
.filter(tx => !tx.account)
|
|
|
|
.reduce((alloc, tx) => {
|
|
|
|
alloc[tx.category] = (alloc[tx.category] || 0) + tx.amount
|
|
|
|
return alloc
|
|
|
|
}, {})
|
|
|
|
},
|
|
|
|
|
2024-01-16 16:30:17 +00:00
|
|
|
categories() {
|
|
|
|
const accounts = this.accounts
|
|
|
|
.filter(acc => acc.type === 'category')
|
|
|
|
.filter(acc => acc.id !== unallocatedMoneyAcc)
|
|
|
|
accounts.sort((a, b) => a.name.localeCompare(b.name))
|
|
|
|
return accounts
|
|
|
|
},
|
|
|
|
|
|
|
|
unallocatedMoney() {
|
|
|
|
const acc = this.accounts.filter(acc => acc.id === unallocatedMoneyAcc)[0] || null
|
|
|
|
if (acc === null) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return acc.balance
|
|
|
|
},
|
|
|
|
|
|
|
|
unallocatedMoneyClass() {
|
|
|
|
const classes = ['d-inline-flex', 'flex-column', 'text-center', 'ms-auto', 'p-2', 'rounded']
|
|
|
|
if (this.unallocatedMoney < 0) {
|
|
|
|
classes.push('bg-danger')
|
|
|
|
} else if (this.unallocatedMoney === 0) {
|
|
|
|
classes.push('bg-warning')
|
|
|
|
} else {
|
|
|
|
classes.push('bg-success')
|
|
|
|
}
|
|
|
|
|
|
|
|
return classes.join(' ')
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2024-01-17 22:02:41 +00:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
timeRange: {},
|
|
|
|
transactions: [],
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
fetchTransactions() {
|
|
|
|
const since = this.timeRange.start.toISOString()
|
|
|
|
const until = this.timeRange.end.toISOString()
|
|
|
|
|
|
|
|
return fetch(`/api/transactions?since=${since}&until=${until}`)
|
|
|
|
.then(resp => resp.json())
|
|
|
|
.then(txs => {
|
|
|
|
this.transactions = txs
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
formatNumber,
|
|
|
|
},
|
|
|
|
|
2024-01-16 16:30:17 +00:00
|
|
|
name: 'AccountingAppBudgetDashboard',
|
|
|
|
|
|
|
|
props: {
|
|
|
|
accounts: {
|
|
|
|
required: true,
|
|
|
|
type: Array,
|
|
|
|
},
|
|
|
|
},
|
2024-01-17 22:02:41 +00:00
|
|
|
|
|
|
|
watch: {
|
|
|
|
timeRange() {
|
|
|
|
this.fetchTransactions()
|
|
|
|
},
|
|
|
|
},
|
2024-01-16 16:30:17 +00:00
|
|
|
}
|
|
|
|
</script>
|