feat(dashboard): add customization
This commit is contained in:
parent
01044538a2
commit
d8ec62c239
16 changed files with 193 additions and 86 deletions
2
Makefile
2
Makefile
|
|
@ -27,7 +27,7 @@ tpl:
|
|||
TEMPL_EXPERIMENT=rawgo templ generate
|
||||
|
||||
lint:
|
||||
npm run lint
|
||||
npm run lint || true
|
||||
npm run format
|
||||
|
||||
.PHONY:
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ const compute = (transactions, cats, dateFrom, dateTo) => {
|
|||
datas[date] = {}
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(datas[date], value.category.id.toString())) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
datas[date],
|
||||
value.category.id.toString(),
|
||||
)
|
||||
) {
|
||||
datas[date][value.category.id.toString()] = 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div v-if="dataValue && dataValue.length">
|
||||
<div
|
||||
v-for="(item, key) in dataValue"
|
||||
v-bind:key="key"
|
||||
:key="key"
|
||||
>
|
||||
<BFormGroup
|
||||
:label="item.label"
|
||||
|
|
@ -18,7 +18,9 @@
|
|||
</div>
|
||||
<div class="col-12 col-md-7 mb-1">
|
||||
<BFormInput
|
||||
v-if="item.type == 'string' && needValue(dataValue[key].comparator)"
|
||||
v-if="
|
||||
item.type == 'string' && needValue(dataValue[key].comparator)
|
||||
"
|
||||
v-model="dataValue[key].value"
|
||||
@change="changed"
|
||||
/>
|
||||
|
|
@ -31,14 +33,18 @@
|
|||
/>
|
||||
|
||||
<BFormInput
|
||||
v-if="item.type == 'number' && needValue(dataValue[key].comparator)"
|
||||
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-if="
|
||||
item.type == 'select' && needValue(dataValue[key].comparator)
|
||||
"
|
||||
v-model="dataValue[key].value"
|
||||
:options="item.options"
|
||||
@change="changed"
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
</div>
|
||||
<div>
|
||||
<Line
|
||||
v-if="data.length > 0"
|
||||
:data="compute(data, precision, dateFrom, dateTo)"
|
||||
:options="options()"
|
||||
:style="chartStyle(300)"
|
||||
/>
|
||||
<div v-else>Aucune donnée</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div class="row mt-3 mb-5">
|
||||
<div
|
||||
v-for="(item, key) in categories"
|
||||
v-bind:key="key"
|
||||
:key="key"
|
||||
class="col-auto checkbox"
|
||||
:class="{'checkbox--unchecked': !selectedCategories[item.label]}"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<option value="">Tous</option>
|
||||
<option
|
||||
v-for="item in accounts"
|
||||
v-bind:key="item.id"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
v-text="item.label"
|
||||
></option>
|
||||
|
|
|
|||
|
|
@ -60,14 +60,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
BTableSimple,
|
||||
BThead,
|
||||
BTbody,
|
||||
BTr,
|
||||
BTh,
|
||||
BTd,
|
||||
} from 'bootstrap-vue-next'
|
||||
import {BTableSimple, BThead, BTbody, BTr, BTh, BTd} from 'bootstrap-vue-next'
|
||||
import {renderCategory, renderEuro} from '../../lib/renderers'
|
||||
|
||||
defineProps(['data'])
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@ const saveStorage = (key, data, name) => {
|
|||
localStorage.setItem(key, JSON.stringify(data))
|
||||
}
|
||||
|
||||
const getStorage = (key) => {
|
||||
const getStorage = (key, defaultValue) => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(key))
|
||||
const value = JSON.parse(localStorage.getItem(key))
|
||||
|
||||
return value === null ? defaultValue : value
|
||||
} catch {
|
||||
return null
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<BTr>
|
||||
<BTh
|
||||
v-for="field in fields"
|
||||
v-bind:key="field.key"
|
||||
:key="field.key"
|
||||
:width="field.width"
|
||||
class="cursor"
|
||||
:class="field.thClasses"
|
||||
|
|
@ -61,10 +61,13 @@
|
|||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr v-for="(row, key) in data.rows" v-bind:key="key">
|
||||
<BTr
|
||||
v-for="(row, key) in data.rows"
|
||||
:key="key"
|
||||
>
|
||||
<BTd
|
||||
v-for="field in fields"
|
||||
v-bind:key="field.key"
|
||||
:key="field.key"
|
||||
class="cursor"
|
||||
:class="field.tdClasses"
|
||||
@click="doEdit(row)"
|
||||
|
|
@ -90,8 +93,8 @@
|
|||
</div>
|
||||
<BModal
|
||||
v-if="form !== null"
|
||||
v-model="formShow"
|
||||
ref="modal"
|
||||
v-model="formShow"
|
||||
scrollable
|
||||
class="modal-xl"
|
||||
:title="form?.label"
|
||||
|
|
@ -106,8 +109,8 @@
|
|||
<BForm @submit="doSave">
|
||||
<BFormGroup
|
||||
v-for="(field, key) in form.fields"
|
||||
v-bind:key="key"
|
||||
:id="'form-label-' + key"
|
||||
:key="key"
|
||||
class="mb-2"
|
||||
:label="field.label"
|
||||
:label-for="'form-label-' + key"
|
||||
|
|
@ -138,7 +141,7 @@
|
|||
>
|
||||
<div
|
||||
v-for="(rule, key) in form.data[field.key]"
|
||||
v-bind:key="key"
|
||||
:key="key"
|
||||
class="list-group-item"
|
||||
>
|
||||
<div class="d-block d-lg-flex justify-content-between gap-1">
|
||||
|
|
|
|||
|
|
@ -8,76 +8,100 @@
|
|||
categories.length > 0
|
||||
"
|
||||
>
|
||||
<Filters
|
||||
v-model:account="account"
|
||||
v-model:date-from="dateFrom"
|
||||
v-model:date-to="dateTo"
|
||||
:accounts="accounts"
|
||||
@update="refresh"
|
||||
/>
|
||||
<div class="d-flex">
|
||||
<Filters
|
||||
v-model:account="account"
|
||||
v-model:date-from="dateFrom"
|
||||
v-model:date-to="dateTo"
|
||||
:accounts="accounts"
|
||||
@update="refresh"
|
||||
/>
|
||||
|
||||
<div class="row text-center">
|
||||
<div
|
||||
class="col-12 col-md-8 mb-4"
|
||||
:class="{'col-md-8': savingAccounts.length > 0}"
|
||||
>
|
||||
<Capital
|
||||
:data="data"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
<div class="ms-auto">
|
||||
<BButton @click="toggleMode()" variant="none">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
</BButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="savingAccounts.length > 0"
|
||||
class="col-12 col-md-4 mb-4"
|
||||
>
|
||||
<SavingAccounts :data="savingAccounts" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="mb-4">
|
||||
<Distribution
|
||||
:data="data"
|
||||
:categories="categories"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4 col-lg-12">
|
||||
<DiffCreditDebit
|
||||
:data="data"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4 col-lg-12">
|
||||
<MonthThresholds
|
||||
:data="monthThresholdsData()"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Draggable v-model="config" class="row" handle=".handle" @change="refresh()">
|
||||
<transition-group>
|
||||
<div
|
||||
v-for="(item, key) in config"
|
||||
:key="key"
|
||||
class="component mb-4 col-12"
|
||||
:class="componentClasses(item)"
|
||||
>
|
||||
<div
|
||||
v-if="mode === 'edit'"
|
||||
class="mb-3 d-flex justify-content-beetween"
|
||||
>
|
||||
<BButton variant="none" size="sm" class="handle">
|
||||
<i class="fa-solid fa-up-down-left-right"></i>
|
||||
</BButton>
|
||||
<BButtonGroup size="sm" class="d-sm-none d-md-block">
|
||||
<BButton
|
||||
v-for="i in [
|
||||
{size: 3, label: '1'},
|
||||
{size: 6, label: '2'},
|
||||
{size: 9, label: '3'},
|
||||
{size: 12, label: '4'},
|
||||
]"
|
||||
:variant="i.size == config[key].size ? 'primary' : 'secondary'"
|
||||
@click="config[key].size = i.size"
|
||||
>{{ i.label }}</BButton>
|
||||
</BButtonGroup>
|
||||
</div>
|
||||
|
||||
<Capital
|
||||
v-if="item.component === 'Capital'"
|
||||
:data="data"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
<SavingAccounts
|
||||
v-if="item.component === 'SavingAccounts'"
|
||||
:data="savingAccounts"
|
||||
/>
|
||||
<Distribution
|
||||
v-if="item.component === 'Distribution'"
|
||||
:data="data"
|
||||
:categories="categories"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
<DiffCreditDebit
|
||||
v-if="item.component === 'DiffCreditDebit'"
|
||||
:data="data"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
<MonthThresholds
|
||||
v-if="item.component === 'MonthThresholds'"
|
||||
:data="monthThresholdsData()"
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
/>
|
||||
</div>
|
||||
</transition-group>
|
||||
</Draggable>
|
||||
</div>
|
||||
<div v-else>Chargement...</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, watch} from 'vue'
|
||||
import {ref, reactive, onMounted, watch} from 'vue'
|
||||
import {compute as monthThresholds} from '../chart/monthThreshold'
|
||||
import {getStorage, saveStorage} from '../lib/storage'
|
||||
import {BButtonGroup, BButton} from 'bootstrap-vue-next'
|
||||
import Filters from './../components/dashboard/Filters.vue'
|
||||
import Capital from './../components/dashboard/Capital.vue'
|
||||
import SavingAccounts from './../components/dashboard/SavingAccounts.vue'
|
||||
import Distribution from './../components/dashboard/Distribution.vue'
|
||||
import MonthThresholds from './../components/dashboard/MonthThresholds.vue'
|
||||
import DiffCreditDebit from './../components/dashboard/DiffCreditDebit.vue'
|
||||
import { VueDraggableNext as Draggable } from 'vue-draggable-next'
|
||||
|
||||
const data = ref(null)
|
||||
const isLoading = ref(true)
|
||||
|
|
@ -86,9 +110,20 @@ const accounts = ref([])
|
|||
const categories = ref([])
|
||||
const savingAccounts = ref([])
|
||||
|
||||
const mode = ref('view')
|
||||
|
||||
const account = ref(getStorage(`dashboard:account`))
|
||||
const dateFrom = ref(getStorage(`dashboard:dateFrom`))
|
||||
const dateTo = ref(getStorage(`dashboard:dateTo`))
|
||||
const config = reactive(
|
||||
getStorage(`dashboard:config`, [
|
||||
{component: 'Capital', size: 8},
|
||||
{component: 'SavingAccounts', size: 4},
|
||||
{component: 'Distribution', size: 12},
|
||||
{component: 'DiffCreditDebit', size: 12},
|
||||
{component: 'MonthThresholds', size: 12},
|
||||
]),
|
||||
)
|
||||
|
||||
let winWidth = 0
|
||||
const _monthThresholdsData = ref(null)
|
||||
|
|
@ -107,9 +142,27 @@ const monthThresholdsData = () => {
|
|||
return _monthThresholdsData.value
|
||||
}
|
||||
|
||||
const toggleMode = () => {
|
||||
mode.value = mode.value === 'edit' ? 'view' : 'edit'
|
||||
}
|
||||
|
||||
const componentClasses = (item) => {
|
||||
const data = [`col-md-${item.size}`]
|
||||
|
||||
if (mode.value === 'edit') {
|
||||
data.push('editable')
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
watch(dateFrom, (v) => saveStorage(`dashboard:dateFrom`, v))
|
||||
watch(dateTo, (v) => saveStorage(`dashboard:dateTo`, v))
|
||||
watch(account, (v) => saveStorage(`dashboard:account`, v))
|
||||
watch(config, (v) => {
|
||||
saveStorage(`dashboard:config`, v)
|
||||
refresh()
|
||||
})
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (Math.abs(window.innerWidth - winWidth) < 20) {
|
||||
|
|
@ -130,6 +183,7 @@ const refresh = () => {
|
|||
limit: 0,
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
_monthThresholdsData.value = null
|
||||
|
||||
if (account.value) {
|
||||
|
|
@ -171,3 +225,14 @@ onMounted(() => {
|
|||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config {
|
||||
width: 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.handle {
|
||||
cursor: grab;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
</div>
|
||||
<div
|
||||
v-for="(item, key) in tree.directories"
|
||||
v-bind:key="key"
|
||||
:key="key"
|
||||
class="p-3 border-bottom flex justify-content-center"
|
||||
>
|
||||
<div class="float-end">
|
||||
|
|
@ -55,7 +55,10 @@
|
|||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in tree.files" v-bind:key="item">
|
||||
<div
|
||||
v-for="item in tree.files"
|
||||
:key="item"
|
||||
>
|
||||
<div class="float-end pt-3 me-3">
|
||||
<BDropdown
|
||||
text=""
|
||||
|
|
@ -120,8 +123,8 @@
|
|||
<BForm @submit="doSave">
|
||||
<BFormGroup
|
||||
v-for="(field, key) in form.fields"
|
||||
v-bind:key="key"
|
||||
:id="'form-label-' + key"
|
||||
:key="key"
|
||||
class="mb-2"
|
||||
:label="field.label"
|
||||
:label-for="'form-label-' + key"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@
|
|||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr v-for="(row, key) in data.rows" :key="key">
|
||||
<BTr
|
||||
v-for="(row, key) in data.rows"
|
||||
:key="key"
|
||||
>
|
||||
<BTd
|
||||
v-for="field in fields"
|
||||
:key="field.key"
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@
|
|||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr v-for="(row, key) in data.rows" :key="key">
|
||||
<BTr
|
||||
v-for="(row, key) in data.rows"
|
||||
:key="key"
|
||||
>
|
||||
<BTd
|
||||
v-for="field in fields"
|
||||
:key="field.key"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@
|
|||
</BTr>
|
||||
</BThead>
|
||||
<BTbody>
|
||||
<BTr v-for="(row, key) in data.rows" :key="key">
|
||||
<BTr
|
||||
v-for="(row, key) in data.rows"
|
||||
:key="key"
|
||||
>
|
||||
<BTd
|
||||
v-for="field in fields"
|
||||
:key="field.key"
|
||||
|
|
@ -88,8 +91,8 @@
|
|||
<BForm @submit="doSave">
|
||||
<BFormGroup
|
||||
v-for="(field, key) in form.fields"
|
||||
v-bind:key="key"
|
||||
:id="'form-label-' + key"
|
||||
:key="key"
|
||||
class="mb-2"
|
||||
:label="field.label"
|
||||
:label-for="'form-label-' + key"
|
||||
|
|
|
|||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
|||
"raw-loader": "^4.0.2",
|
||||
"vue": "^3.4.29",
|
||||
"vue-chartjs": "^5.3.1",
|
||||
"vue-draggable-next": "^2.2.1",
|
||||
"vue-template-compiler": "^2.7.16",
|
||||
"vue3-spinners": "^1.2.2"
|
||||
},
|
||||
|
|
@ -7548,6 +7549,13 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
|
||||
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
|
@ -8122,6 +8130,16 @@
|
|||
"vue": "^3.0.0-0 || ^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-draggable-next": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.2.1.tgz",
|
||||
"integrity": "sha512-EAMS1IRHF0kZO0o5PMOinsQsXIqsrKT1hKmbICxG3UEtn7zLFkLxlAtajcCcUTisNvQ6TtCB5COjD9a1raNADw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"sortablejs": "^1.14.0",
|
||||
"vue": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-eslint-parser": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"raw-loader": "^4.0.2",
|
||||
"vue": "^3.4.29",
|
||||
"vue-chartjs": "^5.3.1",
|
||||
"vue-draggable-next": "^2.2.1",
|
||||
"vue-template-compiler": "^2.7.16",
|
||||
"vue3-spinners": "^1.2.2"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue