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>
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user