fix: fix eslint warnings/errors

This commit is contained in:
Simon Vieille 2025-03-26 11:40:19 +01:00
commit 4918abd5d6
Signed by: deblan
GPG key ID: 579388D585F70417
10 changed files with 911 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>