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:
150
frontend/src/dialogues/falukant/MoneyHistoryGraphDialog.vue
Normal file
150
frontend/src/dialogues/falukant/MoneyHistoryGraphDialog.vue
Normal 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>
|
||||
Reference in New Issue
Block a user