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>

View File

@@ -581,6 +581,22 @@
"time": "Zeit",
"prev": "Zurück",
"next": "Weiter",
"graph": {
"open": "Verlauf anzeigen",
"title": "Geldentwicklung",
"close": "Schließen",
"loading": "Lade Verlauf...",
"noData": "Für den gewählten Zeitraum liegen keine Buchungen vor.",
"range": {
"label": "Zeitraum",
"today": "Heute",
"24h": "Letzte 24 Stunden",
"week": "Letzte Woche",
"month": "Letzter Monat",
"year": "Letztes Jahr",
"all": "Gesamter Verlauf"
}
},
"activities": {
"Product sale": "Produkte verkauft",
"Production cost": "Produktionskosten",

View File

@@ -111,6 +111,22 @@
"time": "Time",
"prev": "Previous",
"next": "Next",
"graph": {
"open": "Show graph",
"title": "Money over time",
"close": "Close",
"loading": "Loading history...",
"noData": "No entries for the selected period.",
"range": {
"label": "Range",
"today": "Today",
"24h": "Last 24 hours",
"week": "Last week",
"month": "Last month",
"year": "Last year",
"all": "All history"
}
},
"activities": {
"Product sale": "Product sale",
"Production cost": "Production cost",

View File

@@ -9,6 +9,12 @@
<button @click="fetchMoneyHistory(1)">{{ $t('falukant.moneyHistory.search') }}</button>
</div>
<div class="graph-section">
<button @click="openGraphDialog">
{{ $t('falukant.moneyHistory.graph.open') }}
</button>
</div>
<table>
<thead>
<tr>
@@ -42,17 +48,21 @@
{{ $t('falukant.moneyHistory.next') }}
</button>
</div>
<MoneyHistoryGraphDialog ref="graphDialog" />
</div>
</template>
<script>
import StatusBar from '@/components/falukant/StatusBar.vue'
import MoneyHistoryGraphDialog from '@/dialogues/falukant/MoneyHistoryGraphDialog.vue'
import apiClient from '@/utils/axios.js';
export default {
name: 'MoneyHistoryView',
components: {
StatusBar,
MoneyHistoryGraphDialog,
},
computed: {
locale() {
@@ -97,6 +107,9 @@ export default {
}
return translation !== key ? translation : activity;
},
openGraphDialog() {
this.$refs.graphDialog.open();
},
},
};
</script>
@@ -106,6 +119,10 @@ export default {
margin-bottom: 1rem;
}
.graph-section {
margin-bottom: 1rem;
}
table {
width: 100%;
border-collapse: collapse;