Feat(home): over time tag spending comparison
This commit is contained in:
parent
cd313264af
commit
7fc1675052
17
src/components/charjs_line.vue
Normal file
17
src/components/charjs_line.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
import { Line, mixins } from 'vue-chartjs'
|
||||||
|
const { reactiveProp } = mixins
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'chartline',
|
||||||
|
extends: Line,
|
||||||
|
mixins: [reactiveProp],
|
||||||
|
props: ['chartData', 'options'],
|
||||||
|
mounted () {
|
||||||
|
this.renderChart(this.chartData, this.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scope>
|
||||||
|
</style>
|
85
src/components/graph_time.vue
Normal file
85
src/components/graph_time.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<chartline :chart-data="datasets" :options="options" v-if="datasets[0] !== 0"></chartline>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
import chartline from './charjs_line'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import { total, groupBy } from '../libs/data_processing'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'graphTime',
|
||||||
|
components: {
|
||||||
|
'chartline': chartline
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
selected_tags: [
|
||||||
|
'virements',
|
||||||
|
'cash',
|
||||||
|
'autoroute',
|
||||||
|
'train',
|
||||||
|
'essence',
|
||||||
|
'courses',
|
||||||
|
'sans tags'
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
legend: {
|
||||||
|
position: 'left'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('datas', {
|
||||||
|
'months': 'months',
|
||||||
|
'tag_filter_rows': 'tag_filter_rows'
|
||||||
|
}),
|
||||||
|
...mapGetters('config', [
|
||||||
|
'tag'
|
||||||
|
]),
|
||||||
|
datasets () {
|
||||||
|
var datas = []
|
||||||
|
this.selected_tags.forEach(t => {
|
||||||
|
var rows = []
|
||||||
|
if (t === 'sans tags') {
|
||||||
|
rows = this.tag_filter_rows([], true, false)
|
||||||
|
console.log(rows)
|
||||||
|
} else {
|
||||||
|
rows = this.tag_filter_rows([t], false, false)
|
||||||
|
}
|
||||||
|
var dateGrouped = groupBy(rows,
|
||||||
|
row => moment(row.Date).format('MMMM YYYY'),
|
||||||
|
total)
|
||||||
|
datas.push({
|
||||||
|
label: t,
|
||||||
|
borderColor: (this.tag(t) ? this.tag(t).color : '#A9A9A9'),
|
||||||
|
data: this.months.map(month => {
|
||||||
|
return (dateGrouped[month] ? dateGrouped[month] : 0)
|
||||||
|
}),
|
||||||
|
fill: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
labels: this.months,
|
||||||
|
datasets: datas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scope>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
height: 420px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -29,8 +29,48 @@ export function formatDate (row, field = 'Date') {
|
|||||||
row[field] = moment(row[field], 'DD/MM/YYYY', true)
|
row[field] = moment(row[field], 'DD/MM/YYYY', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function total (row, field = 'Montant') {
|
export function total (rows, field = 'Montant') {
|
||||||
var sum = row.map(x => parseFloat(x[field]))
|
var sum = rows.map(x => parseFloat(x[field]))
|
||||||
.reduce((sum, x) => sum + x, 0)
|
.reduce((sum, x) => sum + x, 0)
|
||||||
return Math.round(sum)
|
return Math.round(sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tag_filter (rows, tags, invert=false) {
|
||||||
|
// filter rows by tags
|
||||||
|
// invert inverts the selection
|
||||||
|
return rows.filter(row => {
|
||||||
|
if (invert) {
|
||||||
|
return tags.some(t => {
|
||||||
|
return row.tags.map(t => t.name.toLowerCase())
|
||||||
|
.indexOf(t.toLowerCase()) < 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return tags.every(t => {
|
||||||
|
return row.tags.map(t => t.name.toLowerCase())
|
||||||
|
.indexOf(t.toLowerCase()) > -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectMap (object, mapFn) {
|
||||||
|
// map a function on object value
|
||||||
|
return Object.keys(object).reduce(function(result, key) {
|
||||||
|
result[key] = mapFn(object[key])
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupBy (rows, grouping, agg) {
|
||||||
|
// Group rows by field then apply agg
|
||||||
|
var groups = rows.reduce((stock, row) => {
|
||||||
|
var group = grouping(row)
|
||||||
|
if (stock[group]) {
|
||||||
|
stock[group].push(row)
|
||||||
|
} else {
|
||||||
|
stock[group] = [row]
|
||||||
|
}
|
||||||
|
return stock
|
||||||
|
}, {})
|
||||||
|
return objectMap(groups, group => agg(group))
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ import Vue from 'vue'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import Papa from 'papaparse'
|
import Papa from 'papaparse'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { appendTag, formatDate } from '../../libs/data_processing'
|
import { appendTag, formatDate, tag_filter } from '../../libs/data_processing'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -57,27 +57,25 @@ export default {
|
|||||||
return moment(x.Date).isSame(state.month, 'month')
|
return moment(x.Date).isSame(state.month, 'month')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
tag_filter_rows: (state, getters) => (tags, invert) => {
|
tag_filter_rows: (state, getters) => (tags, invert, dateFilter = true) => {
|
||||||
// return rows filtered by date then by tags
|
// return rows filtered by tags
|
||||||
if (tags) {
|
// by default it filters rows by date
|
||||||
return getters.date_filter_rows.filter(row => {
|
// to disable date filtering set date_filter to false
|
||||||
if (invert) {
|
var rows
|
||||||
return tags.some(t => {
|
if (dateFilter) {
|
||||||
return row.tags.map(t => t.name.toLowerCase())
|
rows = getters.date_filter_rows
|
||||||
.indexOf(t.toLowerCase()) < 0
|
} else {
|
||||||
})
|
rows = getters.spending_rows
|
||||||
} else {
|
}
|
||||||
return tags.every(t => {
|
if (tags.length > 0) {
|
||||||
return row.tags.map(t => t.name.toLowerCase())
|
return tag_filter(rows, tags, invert)
|
||||||
.indexOf(t.toLowerCase()) > -1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
if (invert) {
|
if (invert) {
|
||||||
return []
|
return rows.filter(r => {
|
||||||
|
return r.tags.map(t => t.name.toLowerCase()).toString() === ["cb"].toString()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
return getters.date_filter_rows
|
return rows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -99,7 +97,12 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
months: (state, getters) => {
|
||||||
|
// Set of month
|
||||||
|
return [... new Set(getters.rows.map(x => moment(x.Date).format('MMMM YYYY')))]
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
CLEAR_DATA: (state) => {
|
CLEAR_DATA: (state) => {
|
||||||
|
@ -2,50 +2,54 @@
|
|||||||
<div class="import">
|
<div class="import">
|
||||||
<h1>Analyse</h1>
|
<h1>Analyse</h1>
|
||||||
<div class="analysis" v-if="datas_present">
|
<div class="analysis" v-if="datas_present">
|
||||||
<b-container fluid>
|
<h2> Sur le temps </h2>
|
||||||
<b-row class="date-selector">
|
<graph-time></graph-time>
|
||||||
<b-button @click="prev_month" variant="link">
|
|
||||||
<font-awesome-icon icon="angle-left" class="fa" />
|
|
||||||
</b-button>
|
|
||||||
<span class="text-muted btn" disabled="true">
|
|
||||||
{{ month.format('MMMM YYYY') }}
|
|
||||||
</span>
|
|
||||||
<b-button @click="next_month" variant="link">
|
|
||||||
<font-awesome-icon icon="angle-right" class="fa" />
|
|
||||||
</b-button>
|
|
||||||
</b-row>
|
|
||||||
</b-container>
|
|
||||||
|
|
||||||
<b-card-group deck class="mb-3">
|
<h2> Par mois</h2>
|
||||||
<box @click.native="set_tags_filter([])"></box>
|
<b-container fluid>
|
||||||
<box @click.native="set_tags_filter(['cash'])" tagname="cash"></box>
|
<b-row class="date-selector">
|
||||||
<box @click.native="set_tags_filter(['cb'])" tagname="cb"></box>
|
<b-button @click="prev_month" variant="link">
|
||||||
<box @click.native="set_tags_filter(['virements'])" tagname="virements"></box>
|
<font-awesome-icon icon="angle-left" class="fa" />
|
||||||
</b-card-group>
|
</b-button>
|
||||||
|
<span class="text-muted btn" disabled="true">
|
||||||
|
{{ month.format('MMMM YYYY') }}
|
||||||
|
</span>
|
||||||
|
<b-button @click="next_month" variant="link">
|
||||||
|
<font-awesome-icon icon="angle-right" class="fa" />
|
||||||
|
</b-button>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
|
||||||
<tags-comparison></tags-comparison>
|
<b-card-group deck class="mb-3">
|
||||||
|
<box @click.native="set_tags_filter([])"></box>
|
||||||
|
<box @click.native="set_tags_filter(['cash'])" tagname="cash"></box>
|
||||||
|
<box @click.native="set_tags_filter(['cb'])" tagname="cb"></box>
|
||||||
|
<box @click.native="set_tags_filter(['virements'])" tagname="virements"></box>
|
||||||
|
</b-card-group>
|
||||||
|
|
||||||
<b-table striped hover :items="filtered_rows" :fields='fields'>
|
<tags-comparison></tags-comparison>
|
||||||
<template slot="tags" slot-scope="data">
|
|
||||||
<div v-for="tag in data.item.tags" :key="tag.name">
|
<b-table striped hover :items="filtered_rows" :fields='fields'>
|
||||||
<div v-if="tag.name !== 'Tout'">
|
<template slot="tags" slot-scope="data">
|
||||||
<font-awesome-icon :icon="tag.icon" class="fa"/>
|
<div v-for="tag in data.item.tags" :key="tag.name">
|
||||||
</div>
|
<div v-if="tag.name !== 'Tout'">
|
||||||
</div>
|
<font-awesome-icon :icon="tag.icon" class="fa"/>
|
||||||
</template>
|
</div>
|
||||||
</b-table>
|
</div>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="nodata">
|
<div class="nodata">
|
||||||
<h1>
|
<h1>
|
||||||
<font-awesome-icon icon="dizzy" class="fa"/>
|
<font-awesome-icon icon="dizzy" class="fa"/>
|
||||||
Pas de données
|
Pas de données
|
||||||
<font-awesome-icon icon="dizzy" class="fa"/>
|
<font-awesome-icon icon="dizzy" class="fa"/>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
Penser à en importer!
|
Penser à en importer!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -54,6 +58,7 @@
|
|||||||
import { mapGetters, mapActions } from 'vuex'
|
import { mapGetters, mapActions } from 'vuex'
|
||||||
import box from '../components/box'
|
import box from '../components/box'
|
||||||
import tagsComparison from '../components/tags_comparison'
|
import tagsComparison from '../components/tags_comparison'
|
||||||
|
import graphTime from '../components/graph_time'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
moment.locale('fr')
|
moment.locale('fr')
|
||||||
@ -62,7 +67,8 @@ export default {
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
components: {
|
components: {
|
||||||
box: box,
|
box: box,
|
||||||
tagsComparison: tagsComparison
|
tagsComparison: tagsComparison,
|
||||||
|
graphTime: graphTime
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@ -98,6 +104,7 @@ export default {
|
|||||||
'tag_filter_rows': 'datas/tag_filter_rows',
|
'tag_filter_rows': 'datas/tag_filter_rows',
|
||||||
'datas_present': 'datas/present',
|
'datas_present': 'datas/present',
|
||||||
'month': 'datas/month',
|
'month': 'datas/month',
|
||||||
|
'months': 'datas/months',
|
||||||
'tags': 'config/tags'
|
'tags': 'config/tags'
|
||||||
}),
|
}),
|
||||||
filtered_rows () {
|
filtered_rows () {
|
||||||
|
Loading…
Reference in New Issue
Block a user