fix: fix eslint warnings/errors
This commit is contained in:
parent
3a90060d83
commit
4918abd5d6
10 changed files with 911 additions and 0 deletions
195
frontend/js/components/CrudFilter.vue
Normal file
195
frontend/js/components/CrudFilter.vue
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<div v-if="dataValue && dataValue.length">
|
||||
<div
|
||||
v-for="(item, key) in dataValue"
|
||||
:key="key"
|
||||
>
|
||||
<BFormGroup
|
||||
:label="item.label"
|
||||
class="mb-2"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 mb-1">
|
||||
<BFormSelect
|
||||
v-model="dataValue[key].comparator"
|
||||
:options="options(item.type)"
|
||||
@change="changed"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-7 mb-1">
|
||||
<BFormInput
|
||||
v-if="
|
||||
item.type == 'string' && needValue(dataValue[key].comparator)
|
||||
"
|
||||
v-model="dataValue[key].value"
|
||||
@change="changed"
|
||||
/>
|
||||
|
||||
<BFormInput
|
||||
v-if="item.type == 'date' && needValue(dataValue[key].comparator)"
|
||||
v-model="dataValue[key].value"
|
||||
type="date"
|
||||
@change="changed"
|
||||
/>
|
||||
|
||||
<BFormInput
|
||||
v-if="
|
||||
item.type == 'number' && needValue(dataValue[key].comparator)
|
||||
"
|
||||
v-model="dataValue[key].value"
|
||||
type="number"
|
||||
@change="changed"
|
||||
/>
|
||||
|
||||
<BFormSelect
|
||||
v-if="
|
||||
item.type == 'select' && needValue(dataValue[key].comparator)
|
||||
"
|
||||
v-model="dataValue[key].value"
|
||||
:options="item.options"
|
||||
@change="changed"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-1 px-0">
|
||||
<BButton
|
||||
variant="none"
|
||||
@click="doRemove(key)"
|
||||
>
|
||||
<i class="fa fa-trash"></i>
|
||||
</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-center"
|
||||
>
|
||||
Aucun filtre pour le moment.
|
||||
</div>
|
||||
|
||||
<BFormGroup class="mt-5">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<BFormSelect
|
||||
v-model="choice"
|
||||
:options="choices"
|
||||
@change="changed"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<BButton @click="doAdd">Ajouter</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</BFormGroup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {
|
||||
BFormGroup,
|
||||
BFormInput,
|
||||
BFormSelect,
|
||||
BButton,
|
||||
} from 'bootstrap-vue-next'
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
fields: {
|
||||
type: [Array, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const dataValue = ref(props.data.value)
|
||||
const choices = ref([])
|
||||
const choice = ref(null)
|
||||
|
||||
const _ = { value: null, text: null }
|
||||
const eq = { value: 'eq', text: '=' }
|
||||
const neq = { value: 'neq', text: '!=' }
|
||||
const like = { value: 'like', text: 'contient' }
|
||||
const nlike = { value: 'nlike', text: 'ne content pas' }
|
||||
const empty = { value: 'empty', text: 'est vide' }
|
||||
const nEmpty = { value: 'nempty', text: 'est pas vide' }
|
||||
const gt = { value: 'gt', text: '>=' }
|
||||
const lt = { value: 'lt', text: '<=' }
|
||||
|
||||
const stringOptions = [_, eq, neq, like, nlike, empty, nEmpty]
|
||||
const dateOptions = [_, eq, neq, gt, lt, empty, nEmpty]
|
||||
const numberOptions = [_, eq, neq, gt, lt, empty, nEmpty]
|
||||
const selectOptions = [_, eq, neq, empty, nEmpty]
|
||||
|
||||
const options = (type) => {
|
||||
if (type === 'string') {
|
||||
return stringOptions
|
||||
}
|
||||
|
||||
if (type === 'date') {
|
||||
return dateOptions
|
||||
}
|
||||
|
||||
if (type === 'number') {
|
||||
return numberOptions
|
||||
}
|
||||
|
||||
if (type === 'select') {
|
||||
return selectOptions
|
||||
}
|
||||
}
|
||||
|
||||
const needValue = (c) => {
|
||||
return !['empty', 'nempty'].includes(c)
|
||||
}
|
||||
|
||||
const doAdd = () => {
|
||||
if (!choice.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
props.fields.forEach((field) => {
|
||||
if (field.key === choice.value) {
|
||||
dataValue.value.push({
|
||||
key: field.key,
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
value: null,
|
||||
comparator: null,
|
||||
options: field.options ?? [],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
changed()
|
||||
}
|
||||
|
||||
const doRemove = (key) => {
|
||||
let items = []
|
||||
|
||||
dataValue.value.forEach((v, k) => {
|
||||
if (k !== key) {
|
||||
items.push(v)
|
||||
}
|
||||
})
|
||||
|
||||
dataValue.value = items
|
||||
changed()
|
||||
}
|
||||
|
||||
const changed = () => {
|
||||
emit('update', dataValue.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
dataValue.value = props.data
|
||||
|
||||
for (let field of props.fields) {
|
||||
choices.value.push({ value: field.key, text: field.label })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
33
frontend/js/components/crud/CrudHeader.vue
Normal file
33
frontend/js/components/crud/CrudHeader.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="header d-block p-3 mb-3">
|
||||
<div class="d-md-flex justify-content-between menu">
|
||||
<h3>{{ title }}</h3>
|
||||
<slot name="menu"></slot>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots.bottom"
|
||||
class="pt-3"
|
||||
>
|
||||
<slot name="bottom"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.menu .btn {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.menu .btn:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
63
frontend/js/components/crud/CrudPager.vue
Normal file
63
frontend/js/components/crud/CrudPager.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<BPagination
|
||||
:model-value="page"
|
||||
:per-page="limit"
|
||||
:total-rows="limit * pages"
|
||||
size="sm"
|
||||
class="m-0"
|
||||
@page-click="updatePage"
|
||||
/>
|
||||
<BFormSelect
|
||||
:model-value="limit"
|
||||
:options="limits()"
|
||||
:required="true"
|
||||
style="width: 70px"
|
||||
size="sm"
|
||||
@change="updateLimit($event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { BFormSelect, BPagination } from 'bootstrap-vue-next'
|
||||
|
||||
defineProps({
|
||||
page: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
pages: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:page', 'update:limit', 'update'])
|
||||
|
||||
const updatePage = (event, value) => {
|
||||
emit('update:page', parseInt(value))
|
||||
emit('update')
|
||||
}
|
||||
|
||||
const updateLimit = (value) => {
|
||||
emit('update:page', 1)
|
||||
emit('update:limit', parseInt(value))
|
||||
emit('update')
|
||||
}
|
||||
|
||||
const limits = () => {
|
||||
let v = []
|
||||
|
||||
for (let i of [10, 20, 50, 100, 0]) {
|
||||
let label = i !== 0 ? i : 'Tout'
|
||||
v.push({ value: i, text: label })
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
</script>
|
||||
70
frontend/js/components/dashboard/CapitalChart.vue
Normal file
70
frontend/js/components/dashboard/CapitalChart.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div class="stats text-center">
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5 class="mb-2">Capital</h5>
|
||||
<BFormSelect
|
||||
id="precision"
|
||||
v-model="precision"
|
||||
:options="precisions()"
|
||||
></BFormSelect>
|
||||
</div>
|
||||
<div>
|
||||
<Line
|
||||
v-if="data.length > 0"
|
||||
:data="compute(data, precision, dateFrom, dateTo)"
|
||||
:options="options()"
|
||||
:style="chartStyle(380)"
|
||||
/>
|
||||
<div v-else>Aucune donnée</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { getStorage, saveStorage } from '../../lib/storage'
|
||||
import { compute } from '../../chart/capital'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { BFormSelect } from 'bootstrap-vue-next'
|
||||
import { chartStyle } from '../../lib/chartStyle'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateFrom: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
dateTo: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const precision = ref(getStorage('dashboard:capital:precision'))
|
||||
|
||||
watch(precision, (v) => saveStorage('dashboard:capital:precision', v))
|
||||
|
||||
const precisions = () => {
|
||||
return [
|
||||
{ value: 'month', text: 'Mois' },
|
||||
{ value: '2weeks', text: '2 semaines' },
|
||||
{ value: 'day', text: 'Jour' },
|
||||
]
|
||||
}
|
||||
|
||||
const options = () => {
|
||||
return {
|
||||
responsive: false,
|
||||
maintainAspectRatio: true,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#precision {
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
140
frontend/js/components/dashboard/CategoriesChart.vue
Normal file
140
frontend/js/components/dashboard/CategoriesChart.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="stats">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<h5 class="mb-2">Catégories - Statistiques des débits</h5>
|
||||
<BFormSelect
|
||||
id="order"
|
||||
v-model="order"
|
||||
:options="orders"
|
||||
></BFormSelect>
|
||||
</div>
|
||||
<BTableSimple
|
||||
v-if="Object.keys(data).length > 0"
|
||||
caption-top
|
||||
responsive
|
||||
>
|
||||
<BThead>
|
||||
<BTr>
|
||||
<BTh class="text-start">Catégorie</BTh>
|
||||
<BTh
|
||||
width="170px"
|
||||
class="text-end"
|
||||
v-html="
|
||||
renderLabelWithSum(
|
||||
'Total',
|
||||
computed(data, dateFrom, dateTo, order),
|
||||
'sum',
|
||||
)
|
||||
"
|
||||
></BTh>
|
||||
<BTh
|
||||
width="250px"
|
||||
class="text-end"
|
||||
valign="top"
|
||||
>Débit moyen / transaction</BTh
|
||||
>
|
||||
<BTh
|
||||
width="210px"
|
||||
class="text-end"
|
||||
valign="top"
|
||||
>Débit moyen / mois</BTh
|
||||
>
|
||||
<BTh
|
||||
width="170px"
|
||||
class="text-end"
|
||||
valign="top"
|
||||
>Transactions</BTh
|
||||
>
|
||||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr
|
||||
v-for="item in computed(data, dateFrom, dateTo, order)"
|
||||
:key="item.category.id"
|
||||
>
|
||||
<BTd
|
||||
class="text-start"
|
||||
v-html="renderCategory(item.category)"
|
||||
></BTd>
|
||||
<BTd class="text-end">{{ renderEuro(item.sum) }}</BTd>
|
||||
<BTd class="text-end">{{ renderEuro(item.average) }}</BTd>
|
||||
<BTd class="text-end">{{ renderEuro(item.monthAverage) }}</BTd>
|
||||
<BTd class="text-end">{{ item.count }}</BTd>
|
||||
</BTr>
|
||||
</BTbody>
|
||||
</BTableSimple>
|
||||
<div
|
||||
v-else
|
||||
class="text-center"
|
||||
>
|
||||
<p>Aucune donnée.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { getStorage, saveStorage } from '../../lib/storage'
|
||||
import {
|
||||
BFormSelect,
|
||||
BTableSimple,
|
||||
BThead,
|
||||
BTbody,
|
||||
BTr,
|
||||
BTh,
|
||||
BTd,
|
||||
} from 'bootstrap-vue-next'
|
||||
import { compute } from '../../chart/debitAverage'
|
||||
import {
|
||||
renderLabelWithSum,
|
||||
renderCategory,
|
||||
renderEuro,
|
||||
} from '../../lib/renderers'
|
||||
|
||||
const order = ref(getStorage('dashboard:debitAverage:order', 'sum'))
|
||||
const orders = [
|
||||
{ value: 'sum', text: 'Total' },
|
||||
{ value: 'average', text: 'Débit moyen / transaction' },
|
||||
{ value: 'monthAverage', text: 'Débit moyen / mois' },
|
||||
{ value: 'count', text: 'Transactions' },
|
||||
]
|
||||
|
||||
let cache = null
|
||||
let cacheKey = null
|
||||
|
||||
const computed = (data, dateFrom, dateTo, order) => {
|
||||
const key = `${dateFrom}-${dateTo}-${order}`
|
||||
|
||||
if (cache !== null && cacheKey === key) {
|
||||
return cache
|
||||
}
|
||||
|
||||
cacheKey = key
|
||||
cache = compute(data, dateFrom, dateTo, order)
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
watch(order, (v) => saveStorage('dashboard:debitAverage:order', v))
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateFrom: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
dateTo: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#order {
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
115
frontend/js/components/dashboard/DashFilter.vue
Normal file
115
frontend/js/components/dashboard/DashFilter.vue
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-2 mb-1">
|
||||
<select
|
||||
:value="account"
|
||||
class="form-control"
|
||||
@change="change('update:account', $event.target.value)"
|
||||
>
|
||||
<option value="">Tous</option>
|
||||
<option
|
||||
v-for="item in accounts"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
v-text="item.label"
|
||||
></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-2 mb-1">
|
||||
<BFormInput
|
||||
ref="date-from"
|
||||
:model-value="dateFrom"
|
||||
type="date"
|
||||
@change="change('update:dateFrom', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-2 mb-1">
|
||||
<BFormInput
|
||||
ref="date-to"
|
||||
:model-value="dateTo"
|
||||
type="date"
|
||||
@change="change('update:dateTo', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-2 mb-1">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="doDateSet(1)"
|
||||
>
|
||||
1m
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="doDateSet(3)"
|
||||
>
|
||||
3m
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="doDateSet(6)"
|
||||
>
|
||||
6m
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="doDateSet(12)"
|
||||
>
|
||||
1a
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toRefs } from 'vue'
|
||||
import { BFormInput } from 'bootstrap-vue-next'
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:account',
|
||||
'update:dateFrom',
|
||||
'update:dateTo',
|
||||
'update',
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
account: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
accounts: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateFrom: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
dateTo: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { dateFrom, dateTo } = toRefs(props)
|
||||
|
||||
const change = (event, value) => {
|
||||
emit(event, value)
|
||||
emit('update')
|
||||
}
|
||||
|
||||
const doDateSet = (month) => {
|
||||
const d = new Date()
|
||||
|
||||
d.setUTCMonth(d.getUTCMonth() + 1 - month)
|
||||
let m = d.getUTCMonth() + 1
|
||||
|
||||
if (m < 10) {
|
||||
m = `0${m}`
|
||||
}
|
||||
|
||||
change('update:dateFrom', `${d.getUTCFullYear()}-${m}-01`)
|
||||
change('update:dateTo', null)
|
||||
}
|
||||
</script>
|
||||
41
frontend/js/components/dashboard/DiffCreditDebitChart.vue
Normal file
41
frontend/js/components/dashboard/DiffCreditDebitChart.vue
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div class="stats text-center">
|
||||
<h5 class="mb-2">Différences des crédits et des débits</h5>
|
||||
<div>
|
||||
<Bar
|
||||
class="chart"
|
||||
:data="compute(data, dateFrom, dateTo)"
|
||||
:options="options()"
|
||||
:style="chartStyle(380)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Bar } from 'vue-chartjs'
|
||||
import { compute } from '../../chart/diffCreditDebit'
|
||||
import { chartStyle } from '../../lib/chartStyle'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateFrom: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
dateTo: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const options = () => {
|
||||
return {
|
||||
responsive: false,
|
||||
maintainAspectRatio: true,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
145
frontend/js/components/dashboard/DistributionChart.vue
Normal file
145
frontend/js/components/dashboard/DistributionChart.vue
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<div class="stats">
|
||||
<h5 class="mb-2 text-center">Répartion des dépenses</h5>
|
||||
|
||||
<div class="row mt-3 mb-5">
|
||||
<div
|
||||
v-for="(item, key) in categories"
|
||||
:key="key"
|
||||
class="col-auto checkbox"
|
||||
:class="{ 'checkbox--unchecked': !selectedCategories[item.label] }"
|
||||
>
|
||||
<BFormCheckbox v-model="selectedCategories[item.label]">
|
||||
<span
|
||||
class="cursor"
|
||||
v-html="renderCategory(item)"
|
||||
></span>
|
||||
</BFormCheckbox>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<span
|
||||
class="cursor fw-bold"
|
||||
@click="selectAllCategories()"
|
||||
>Toutes</span
|
||||
>
|
||||
-
|
||||
<span
|
||||
class="cursor fw-bold"
|
||||
@click="unSelectAllCategories()"
|
||||
>Aucune</span
|
||||
>
|
||||
-
|
||||
<span
|
||||
class="cursor fw-bold"
|
||||
@click="inverseCategories()"
|
||||
>Inverser</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-4">
|
||||
<Doughnut
|
||||
:data="computeDoughnut(data, dateFrom, dateTo, selectedCategories)"
|
||||
:options="fixedOptions({ plugins: { legend: { display: false } } })"
|
||||
:style="chartStyle(380)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="d-flex justify-content-between">
|
||||
<BFormCheckbox v-model="isStacked"> En pourcentage </BFormCheckbox>
|
||||
</div>
|
||||
<div>
|
||||
<Bar
|
||||
:data="
|
||||
computeBar(data, isStacked, dateFrom, dateTo, selectedCategories)
|
||||
"
|
||||
:options="stackOptions({ plugins: { legend: { display: false } } })"
|
||||
:style="chartStyle(380)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, toRefs, watch, onMounted } from 'vue'
|
||||
import { getStorage, saveStorage } from '../../lib/storage'
|
||||
import { computeDoughnut, computeBar } from '../../chart/distribution'
|
||||
import { renderCategory } from '../../lib/renderers'
|
||||
import { Doughnut, Bar } from 'vue-chartjs'
|
||||
import { BFormCheckbox } from 'bootstrap-vue-next'
|
||||
import { chartStyle } from '../../lib/chartStyle'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
categories: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateFrom: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
dateTo: {
|
||||
type: [String, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const isStacked = ref(getStorage('dashboard:distribution:isStacked'))
|
||||
const selectedCategories = ref({})
|
||||
const { categories } = toRefs(props)
|
||||
|
||||
watch(isStacked, (v) => saveStorage('dashboard:distribution:isStacked', v))
|
||||
|
||||
const fixedOptions = (opts) => {
|
||||
let value = opts || {}
|
||||
|
||||
return {
|
||||
...value,
|
||||
responsive: false,
|
||||
maintainAspectRatio: true,
|
||||
}
|
||||
}
|
||||
|
||||
const stackOptions = (opts) => {
|
||||
let options = { ...fixedOptions(opts) }
|
||||
|
||||
options.scales = {
|
||||
y: { stacked: true, ticks: { beginAtZero: true } },
|
||||
x: { stacked: true },
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const inverseCategories = () => {
|
||||
for (let i in selectedCategories.value) {
|
||||
selectedCategories.value[i] = !selectedCategories.value[i]
|
||||
}
|
||||
}
|
||||
|
||||
const selectAllCategories = () => {
|
||||
for (let i in selectedCategories.value) {
|
||||
selectedCategories.value[i] = true
|
||||
}
|
||||
}
|
||||
|
||||
const unSelectAllCategories = () => {
|
||||
for (let i in selectedCategories.value) {
|
||||
selectedCategories.value[i] = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
for (let item of categories.value) {
|
||||
selectedCategories.value[item.label] = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
72
frontend/js/components/dashboard/MonthThresholdsTable.vue
Normal file
72
frontend/js/components/dashboard/MonthThresholdsTable.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div class="stats">
|
||||
<h5 class="mb-2 text-center">Dépassements de budgets</h5>
|
||||
<BTableSimple
|
||||
v-if="Object.keys(data).length > 0"
|
||||
caption-top
|
||||
responsive
|
||||
>
|
||||
<BThead>
|
||||
<BTr>
|
||||
<BTh
|
||||
width="120px"
|
||||
class="text-start"
|
||||
>Mois</BTh
|
||||
>
|
||||
<BTh class="text-start">Catégorie</BTh>
|
||||
<BTh
|
||||
width="120px"
|
||||
class="text-end"
|
||||
>Seuil</BTh
|
||||
>
|
||||
<BTh
|
||||
width="120px"
|
||||
class="text-end"
|
||||
>Montant</BTh
|
||||
>
|
||||
<BTh
|
||||
width="120px"
|
||||
class="text-end"
|
||||
>Dépassement</BTh
|
||||
>
|
||||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<template v-for="(item, date) in data">
|
||||
<BTr v-for="row in item">
|
||||
<BTd class="text-start">{{ date }}</BTd>
|
||||
<BTd
|
||||
class="text-start"
|
||||
v-html="renderCategory(row.category)"
|
||||
></BTd>
|
||||
<BTd class="text-end">{{
|
||||
renderEuro(row.category.month_threshold)
|
||||
}}</BTd>
|
||||
<BTd class="text-end">{{ renderEuro(row.amount) }}</BTd>
|
||||
<BTd class="text-end">{{
|
||||
renderEuro(row.amount - row.category.month_threshold)
|
||||
}}</BTd>
|
||||
</BTr>
|
||||
</template>
|
||||
</BTbody>
|
||||
</BTableSimple>
|
||||
<div
|
||||
v-else
|
||||
class="text-center"
|
||||
>
|
||||
<p>Aucune donnée.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { BTableSimple, BThead, BTbody, BTr, BTh, BTd } from 'bootstrap-vue-next'
|
||||
import { renderCategory, renderEuro } from '../../lib/renderers'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
37
frontend/js/components/dashboard/SavingAccountsChart.vue
Normal file
37
frontend/js/components/dashboard/SavingAccountsChart.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="stats text-center">
|
||||
<h5 class="mb-2">Épargnes</h5>
|
||||
<div>
|
||||
<Bar
|
||||
:data="compute(data)"
|
||||
:options="options()"
|
||||
:style="chartStyle(380)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { compute } from '../../chart/savingAccount'
|
||||
import { Bar } from 'vue-chartjs'
|
||||
import { chartStyle } from '../../lib/chartStyle'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
const options = () => {
|
||||
return {
|
||||
responsive: false,
|
||||
maintainAspectRatio: true,
|
||||
indexAxis: 'y',
|
||||
scales: {
|
||||
y: { stacked: true, ticks: { beginAtZero: true } },
|
||||
x: { stacked: true },
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue