accounting/frontend/components/budgetDashboard.vue

152 lines
3.8 KiB
Vue
Raw Normal View History

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>