Rebuild frontend.

This commit is contained in:
James Cole
2021-02-13 20:04:18 +01:00
parent 1ecc454f70
commit 45f918963e
22 changed files with 259 additions and 115 deletions

View File

@@ -57,6 +57,16 @@
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount) }}
</span>
</div>
<!-- amount if bar is very small -->
<span v-if="budgetLimit.pctGreen <= 35 && 0 === budgetLimit.pctOrange && 0 === budgetLimit.pctRed">
&nbsp;
Spent
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent) }}
of
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount) }}
</span>
</div>
<small class="d-none d-lg-block">
{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(budgetLimit.start) }}

View File

@@ -78,22 +78,43 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "Calendar",
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
computed: {
...mapGetters([
'viewRange',
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.range.start = new Date(this.start);
this.range.end = new Date(this.end);
}
},
},
data() {
return {
locale: 'en-US',
ready: false,
range: {
start: new Date(window.sessionStart),
end: new Date(window.sessionEnd),
},
defaultRange: {
start: new Date(window.sessionStart),
end: new Date(window.sessionEnd),
},
start: new Date,
end: new Date,
}
};
},
}

View File

@@ -80,8 +80,11 @@ export default {
methods: {},
watch: {
start: function (value) {
console.log('Value of start is now ' + value);
}
// console.log('Value of start is now ' + value);
},
end: function (value) {
// console.log('Value of end is now ' + value);
},
}
}
</script>

View File

@@ -24,8 +24,14 @@
<h3 class="card-title">{{ $t('firefly.yourAccounts') }}</h3>
</div>
<div class="card-body">
<div>
<canvas id="mainAccountsChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
<div v-if="!loading">
<MainAccountChart :chart-data="dataCollection" :options="chartOptions" v-if="!loading && !error" />
</div>
<div v-if="loading && !error" class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
<div v-if="error" class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
<div class="card-footer">
@@ -38,21 +44,63 @@
import DataConverter from "../charts/DataConverter";
import DefaultLineOptions from "../charts/DefaultLineOptions";
import {createNamespacedHelpers} from "vuex";
import MainAccountChart from "./MainAccountChart";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainAccount",
components: {MainAccountChart},
data() {
return {
loading: true,
error: false,
ready: false,
dataCollection: {},
chartOptions: {}
}
},
created() {
axios.get('./api/v1/chart/account/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
let lineChartCanvas = $('#mainAccountsChart').get(0).getContext('2d');
new Chart(lineChartCanvas, {
type: 'line',
data: chartData,
options: DefaultLineOptions.methods.getDefaultOptions()
this.ready= true;
this.chartOptions = DefaultLineOptions.methods.getDefaultOptions();
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
// console.log(this.chartOptions);
this.initialiseChart();
}
}
},
methods: {
initialiseChart: function () {
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
let url = './api/v1/chart/account/overview?start=' + startStr + '&end=' + endStr;
// console.log('URL is ' + url);
axios.get(url)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
this.dataCollection = chartData;
this.loading = false;
})
.catch(error => {
// console.log('Has error!');
// console.log(error);
this.error = true;
// console.error(error);
});
});
}
},
}
</script>

View File

@@ -20,21 +20,15 @@
-->
<script>
import {Line} from 'vue-chartjs'
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
props: ['options', 'chartData'],
created() {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options)
}
}
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted () {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style scoped>
</style>

View File

@@ -32,9 +32,17 @@
</div>
</div>
<div class="card-body table-responsive p-0">
<transaction-list-large :transactions="account.transactions" v-if="1===accounts.length" :account_id="account.id"/>
<transaction-list-medium :transactions="account.transactions" v-if="2===accounts.length" :account_id="account.id"/>
<transaction-list-small :transactions="account.transactions" v-if="accounts.length > 2" :account_id="account.id"/>
<div v-if="!loading && !error">
<transaction-list-large :transactions="account.transactions" v-if="1===accounts.length" :account_id="account.id"/>
<transaction-list-medium :transactions="account.transactions" v-if="2===accounts.length" :account_id="account.id"/>
<transaction-list-small :transactions="account.transactions" v-if="accounts.length > 2" :account_id="account.id"/>
</div>
<div v-if="loading && !error" class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
<div v-if="error" class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
</div>
</div>
@@ -46,6 +54,9 @@ export default {
name: "MainAccountList",
data() {
return {
loading: true,
error: false,
ready: false,
accounts: [],
locale: 'en-US'
}
@@ -92,6 +103,8 @@ export default {
axios.get('./api/v1/accounts/' + accountId + '/transactions?page=1&limit=10')
.then(response => {
this.accounts[key].transactions = response.data.data;
this.loading = false;
this.error = false;
}
);
},

View File

@@ -62,21 +62,45 @@
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBillsList",
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
// console.log(this.chartOptions);
this.initialiseBills();
}
}
},
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
axios.get('./api/v1/bills?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
this.loadBills(response.data.data);
}
);
},
components: {},
methods: {
initialiseBills: function () {
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
axios.get('./api/v1/bills?start=' + startStr + '&end=' + endStr)
.then(response => {
this.loadBills(response.data.data);
}
);
},
renderPaidDate: function (obj) {
// console.log(obj);
let dateStr = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(new Date(obj.date));
let str = this.$t('firefly.bill_paid_on', {date: dateStr});
return '<a href="./transactions/show/' + obj.transaction_group_id + '" title="' + str + '">' + str + '</a>';
@@ -97,7 +121,8 @@ export default {
data() {
return {
bills: [],
locale: 'en-US'
locale: 'en-US',
ready: false
}
},
}

View File

@@ -19,39 +19,40 @@
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.budgets') }}</h3>
</div>
<div class="card-body">
<div style="position: relative;">
<canvas id="mainBudgetChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
</div>
</div>
<div class="card-footer">
<a href="./budgets" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.budgets') }}</h3>
</div>
<div class="card-body">
<div style="position: relative;">
<canvas id="mainBudgetChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
</div>
</div>
<div class="card-footer">
<a href="./budgets" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
</div>
</div>
</template>
<script>
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainBudget",
created() {
axios.get('./api/v1/chart/budget/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
let stackedBarChartCanvas = $('#mainBudgetChart').get(0).getContext('2d')
new Chart(stackedBarChartCanvas, {
type: 'bar',
data: chartData,
options: DefaultBarOptions.methods.getDefaultOptions()
});
});
},
}
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainBudget",
created() {
axios.get('./api/v1/chart/budget/overview?start=' + window.sessionXStart + '&end=' + window.sessionXEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
let stackedBarChartCanvas = $('#mainBudgetChart').get(0).getContext('2d')
new Chart(stackedBarChartCanvas, {
type: 'bar',
data: chartData,
options: DefaultBarOptions.methods.getDefaultOptions()
});
});
},
}
</script>
<style scoped>

View File

@@ -57,6 +57,8 @@
<script>
import BudgetListGroup from "./BudgetListGroup";
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBudgetList",
@@ -76,11 +78,28 @@ export default {
budgets: {},
rawBudgets: [],
locale: 'en-US',
ready: false
}
},
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
this.collectData();
},
watch: {
datesReady: function (value) {
if (true === value) {
this.collectData();
}
}
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
methods:
{
@@ -88,7 +107,9 @@ export default {
this.getBudgets();
},
getBudgets() {
axios.get('./api/v1/budgets?start=' + window.sessionStart + '&end=' + window.sessionEnd)
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
axios.get('./api/v1/budgets?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgets(response.data);
}
@@ -120,7 +141,9 @@ export default {
getBudgetLimits() {
axios.get('./api/v1/budgets/limits?start=' + window.sessionStart + '&end=' + window.sessionEnd)
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
axios.get('./api/v1/budgets/limits?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgetLimits(response.data);
}

View File

@@ -53,7 +53,7 @@ const actions = {
context.dispatch('setDatesFromViewRange');
}
).catch(error => {
console.log(error);
// console.log(error);
context.commit('setViewRange', '1M');
// call another action:
context.dispatch('setDatesFromViewRange');
@@ -61,24 +61,23 @@ const actions = {
}
},
setDatesFromViewRange(context) {
console.log('Must set dates from viewRange "' + context.state.viewRange + '"');
// console.log('Must set dates from viewRange "' + context.state.viewRange + '"');
// check local storage first?
if (localStorage.viewRangeStart) {
console.log('view range set from local storage.');
context.commit('setStart', localStorage.viewRangeStart);
// console.log('view range set from local storage.');
context.commit('setStart', new Date(localStorage.viewRangeStart));
}
if (localStorage.viewRangeEnd) {
console.log('view range set from local storage.');
context.commit('setEnd', localStorage.viewRangeEnd);
// console.log('view range set from local storage.');
context.commit('setEnd', new Date(localStorage.viewRangeEnd));
}
if (null !== context.getters.end && null !== context.getters.start) {
return;
}
console.log('view range must be calculated.');
let start;
let end;
let viewRange = context.getters.viewRange;
viewRange = '1Y';
// console.log('Will recreate view range on ' + viewRange);
switch (viewRange) {
case '1D':
// one day:
@@ -113,10 +112,10 @@ const actions = {
// this quarter
start = new Date;
end = new Date;
let quarter = Math.floor((start.getMonth() + 3) / 3)-1;
let quarter = Math.floor((start.getMonth() + 3) / 3) - 1;
// start and end months? I'm sure this could be better:
let startMonths = [0,3,6,9];
let endMonths = [2,5,8,11];
let startMonths = [0, 3, 6, 9];
let endMonths = [2, 5, 8, 11];
// set start to the correct month, day one:
start = new Date(start.getFullYear(), startMonths[quarter], 1);
start.setHours(0, 0, 0, 0);
@@ -131,10 +130,10 @@ const actions = {
// this half-year
start = new Date;
end = new Date;
let half = start.getMonth()<= 5 ? 0 : 1;
let half = start.getMonth() <= 5 ? 0 : 1;
let startHalf = [0,6];
let endHalf = [5,11];
let startHalf = [0, 6];
let endHalf = [5, 11];
// set start to the correct month, day one:
start = new Date(start.getFullYear(), startHalf[half], 1);
start.setHours(0, 0, 0, 0);
@@ -156,9 +155,9 @@ const actions = {
end.setHours(23, 59, 59, 999);
break;
}
console.log('Range is ' + viewRange);
console.log('Start is ' + start);
console.log('End is ' + end);
// console.log('Range is ' + viewRange);
// console.log('Start is ' + start);
// console.log('End is ' + end);
context.commit('setStart', start);
context.commit('setEnd', end);
}
@@ -168,9 +167,11 @@ const actions = {
const mutations = {
setStart(state, value) {
state.start = value;
window.localStorage.setItem('viewRangeStart', value);
},
setEnd(state, value) {
state.end = value;
window.localStorage.setItem('viewRangeEnd', value);
},
setViewRange(state, range) {
state.viewRange = range;

View File

@@ -1723,9 +1723,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181:
version "1.0.30001185"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz#3482a407d261da04393e2f0d61eefbc53be43b95"
integrity sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==
version "1.0.30001187"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001187.tgz#5706942631f83baa5a0218b7dfa6ced29f845438"
integrity sha512-w7/EP1JRZ9552CyrThUnay2RkZ1DXxKe/Q2swTC4+LElLh9RRYrL1Z+27LlakB8kzY0fSmHw9mc7XYDUKAKWMA==
chalk@^1.1.3:
version "1.1.3"
@@ -2626,9 +2626,9 @@ ejs@^2.6.1:
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
electron-to-chromium@^1.3.649:
version "1.3.663"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.663.tgz#dd54adfd8d7f0e01b80d236c6e232efbaa0c686c"
integrity sha512-xkVkzHj6k3oRRGlmdgUCCLSLhtFYHDCTH7SeK+LJdJjnsLcrdbpr8EYmfMQhez3V/KPO5UScSpzQ0feYX6Qoyw==
version "1.3.664"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.664.tgz#8fb039e2fa8ef3ab2568308464a28425d4f6e2a3"
integrity sha512-yb8LrTQXQnh9yhnaIHLk6CYugF/An50T20+X0h++hjjhVfgSp1DGoMSYycF8/aD5eiqS4QwaNhiduFvK8rifRg==
elliptic@^6.5.3:
version "6.5.4"
@@ -4469,11 +4469,16 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime-db@1.45.0, "mime-db@>= 1.43.0 < 2":
mime-db@1.45.0:
version "1.45.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
"mime-db@>= 1.43.0 < 2":
version "1.46.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee"
integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==
mime-types@~2.1.17, mime-types@~2.1.24:
version "2.1.28"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"