Add money history graph feature: Implement moneyHistoryGraph method in FalukantService and corresponding controller and router updates. Enhance frontend with a new dialog for displaying money history over various time ranges, including localization updates for German and English. This improves user experience by providing visual insights into financial data.

This commit is contained in:
Torsten Schulz (local)
2026-01-29 10:40:13 +01:00
parent 506a9cd9c0
commit b3db65d1b8
7 changed files with 260 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
<template>
<DialogWidget
ref="dialog"
:title="$t('falukant.moneyHistory.graph.title')"
:isTitleTranslated="true"
:show-close="true"
:buttons="[{ text: 'message.close', action: 'close' }]"
:modal="true"
width="600px"
height="400px"
name="MoneyHistoryGraphDialog"
>
<div class="graph-content">
<div class="graph-controls">
<label>
{{ $t('falukant.moneyHistory.graph.range.label') }}
<select v-model="graphRange" @change="loadGraphData">
<option value="today">{{ $t('falukant.moneyHistory.graph.range.today') }}</option>
<option value="24h">{{ $t('falukant.moneyHistory.graph.range.24h') }}</option>
<option value="week">{{ $t('falukant.moneyHistory.graph.range.week') }}</option>
<option value="month">{{ $t('falukant.moneyHistory.graph.range.month') }}</option>
<option value="year">{{ $t('falukant.moneyHistory.graph.range.year') }}</option>
<option value="all">{{ $t('falukant.moneyHistory.graph.range.all') }}</option>
</select>
</label>
</div>
<div v-if="graphLoading" class="graph-loading">
{{ $t('falukant.moneyHistory.graph.loading') }}
</div>
<div v-else-if="!graphData.length" class="graph-no-data">
{{ $t('falukant.moneyHistory.graph.noData') }}
</div>
<div v-else class="graph-container">
<svg viewBox="0 0 100 40" preserveAspectRatio="none" class="graph-svg">
<polyline
:points="graphPolylinePoints"
fill="none"
stroke="#F9A22C"
stroke-width="0.7"
/>
</svg>
</div>
</div>
</DialogWidget>
</template>
<script>
import DialogWidget from '@/components/DialogWidget.vue'
import apiClient from '@/utils/axios.js'
export default {
name: 'MoneyHistoryGraphDialog',
components: {
DialogWidget,
},
data() {
return {
graphRange: '24h',
graphData: [],
graphLoading: false,
}
},
computed: {
graphPolylinePoints() {
if (!this.graphData.length) return ''
const xs = this.graphData.map(d => new Date(d.time).getTime())
const ys = this.graphData.map(d => {
if (d.moneyAfter != null) return Number(d.moneyAfter)
if (d.moneyBefore != null) return Number(d.moneyBefore)
return 0
})
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
const spanX = maxX - minX || 1
const spanY = maxY - minY || 1
return xs.map((x, i) => {
const normX = ((x - minX) / spanX) * 100
const normY = 40 - ((ys[i] - minY) / spanY) * 35 - 2 // etwas Rand oben/unten
return `${normX.toFixed(2)},${normY.toFixed(2)}`
}).join(' ')
},
},
methods: {
open() {
this.$refs.dialog.open()
this.loadGraphData()
},
async loadGraphData() {
this.graphLoading = true
try {
const response = await apiClient.post('/api/falukant/moneyhistory/graph', {
range: this.graphRange,
})
this.graphData = Array.isArray(response.data) ? response.data : []
} catch (error) {
console.error('Error loading money history graph data:', error)
this.graphData = []
} finally {
this.graphLoading = false
}
},
},
}
</script>
<style scoped>
.graph-content {
padding: 0.5rem;
}
.graph-controls {
margin-bottom: 1rem;
}
.graph-controls label {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.graph-controls select {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.graph-container {
width: 100%;
height: 220px;
margin-top: 1rem;
}
.graph-svg {
width: 100%;
height: 100%;
background: #fdf1db;
border-radius: 4px;
}
.graph-loading,
.graph-no-data {
margin: 1rem 0;
text-align: center;
color: #666;
}
</style>