feat(history): add electricity price chart

This commit is contained in:
2026-08-02 15:52:22 +02:00
parent f70f3ab054
commit 029c29afd1
10 changed files with 137 additions and 20 deletions
+3 -4
View File
@@ -9,12 +9,11 @@ use Spatie\LaravelData\Data;
class HistoryData extends Data class HistoryData extends Data
{ {
/**
* @property Collection<ElectricityData> $electricity
* @property Collection<GasData> $gas
*/
public function __construct( public function __construct(
/** @var Collection<ElectricityData> */
public Collection $electricity, public Collection $electricity,
/** @var Collection<GasData> */
public Collection $gas, public Collection $gas,
) {} ) {}
} }
+31 -1
View File
@@ -10,6 +10,7 @@
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"axios": "^1.19.0", "axios": "^1.19.0",
"chart.js": "^4.5.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"i18next": "^26.3.6", "i18next": "^26.3.6",
@@ -20,7 +21,8 @@
"tailwindcss": "^4.1.1", "tailwindcss": "^4.1.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^8.0.0", "vite": "^8.0.0",
"vue": "^3.5.13" "vue": "^3.5.13",
"vue-chartjs": "^5.3.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
@@ -484,6 +486,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@laravel/vite-plugin-wayfinder": { "node_modules/@laravel/vite-plugin-wayfinder": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/@laravel/vite-plugin-wayfinder/-/vite-plugin-wayfinder-0.1.7.tgz", "resolved": "https://registry.npmjs.org/@laravel/vite-plugin-wayfinder/-/vite-plugin-wayfinder-0.1.7.tgz",
@@ -2420,6 +2428,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/chart.js": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -6856,6 +6876,16 @@
} }
} }
}, },
"node_modules/vue-chartjs": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.4.tgz",
"integrity": "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g==",
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "10.4.1", "version": "10.4.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz",
+3 -1
View File
@@ -34,6 +34,7 @@
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"axios": "^1.19.0", "axios": "^1.19.0",
"chart.js": "^4.5.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"i18next": "^26.3.6", "i18next": "^26.3.6",
@@ -44,7 +45,8 @@
"tailwindcss": "^4.1.1", "tailwindcss": "^4.1.1",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^8.0.0", "vite": "^8.0.0",
"vue": "^3.5.13" "vue": "^3.5.13",
"vue-chartjs": "^5.3.4"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5", "@rollup/rollup-linux-x64-gnu": "4.9.5",
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { computed } from 'vue';
import { Line } from 'vue-chartjs';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend,
Filler,
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
Legend,
Filler,
);
export interface PricePoint {
label: string;
value: number;
}
const props = defineProps<{
data: PricePoint[];
}>();
const chartData = computed(() => ({
labels: props.data.map((d) => d.label),
datasets: [
{
data: props.data.map((d) => d.value),
borderColor: '#4ba66a',
backgroundColor: 'rgba(75, 166, 106, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 2,
},
],
}));
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
};
</script>
<template>
<div class="flex h-64 flex-1">
<Line :data="chartData" :options="chartOptions" />
</div>
</template>
+14 -4
View File
@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import useHistoryViewModel from '../viewmodels/HistoryViewModel'; import useHistoryViewModel from '../viewmodels/HistoryViewModel';
import moment from 'moment'; import moment from 'moment';
import Chart from '../components/Chart.vue';
const model = useHistoryViewModel(); const model = useHistoryViewModel();
@@ -14,10 +15,19 @@ onMounted(() => {
watch(date, (value) => { watch(date, (value) => {
model.getHistoryData(moment(value).format('YYYY-MM-DD')); model.getHistoryData(moment(value).format('YYYY-MM-DD'));
}); });
const chartData = computed(
() =>
model.historyData.value?.electricity
.filter((e) => e.totalPriceTaxIncluded != null)
.map((e) => ({
label: moment(e.startDate).format('HH:mm'),
value: e.totalPriceTaxIncluded,
})) ?? [],
);
</script> </script>
<template> <template>
<div> <input type="date" v-model="date" />
<input type="date" v-model="date" /> <Chart :data="chartData" />
</div>
</template> </template>
@@ -1,6 +1,7 @@
import { create } from 'axios'; import { create } from 'axios';
import { ref } from 'vue'; import { ref } from 'vue';
import HistoryData = App.Features.History.Data.HistoryData; import HistoryData = App.Features.History.Data.HistoryData;
import moment from 'moment';
export default function useHistoryViewModel() { export default function useHistoryViewModel() {
const isLoading = ref<boolean>(false); const isLoading = ref<boolean>(false);
@@ -17,6 +18,18 @@ export default function useHistoryViewModel() {
}, },
}) })
.then((res) => { .then((res) => {
console.log(
res.data?.electricity.flatMap((e) => {
if (!e.totalPriceTaxIncluded) {
return;
}
return {
label: moment(e.startDate).format('DD-MM-YYYY'),
value: e.totalPriceTaxIncluded?.toPrecision(2),
};
}),
);
historyData.value = res.data; historyData.value = res.data;
}) })
.finally(() => (isLoading.value = false)); .finally(() => (isLoading.value = false));
+6 -6
View File
@@ -17,10 +17,10 @@ declare namespace App {
startDate: string; startDate: string;
endDate: string; endDate: string;
period: string; period: string;
marketPrice: number; marketPrice: number | null;
totalPriceTaxIncluded: number; totalPriceTaxIncluded: number | null;
priceInclHandlingVat: number; priceInclHandlingVat: number | null;
priceTaxWithVat: number; priceTaxWithVat: number | null;
startDateDatetime: string; startDateDatetime: string;
}; };
} }
@@ -41,8 +41,8 @@ declare namespace App {
namespace History { namespace History {
namespace Data { namespace Data {
export type HistoryData = { export type HistoryData = {
electricity: Array<any>; electricity: App.Data.ElectricityData[];
gas: Array<any>; gas: App.Data.GasData[];
}; };
} }
namespace Requests { namespace Requests {
@@ -1,3 +1,3 @@
{ {
"generated.d.ts": "7ca8a9c7f0ae5e5498bdee87bb2f005d" "generated.d.ts": "596cfad66b8ff3f8b6b98271630919ae"
} }
+2 -2
View File
@@ -1,9 +1,9 @@
import i18next from 'i18next'; import i18next from 'i18next';
import resources from './translations'; import resources from './translations';
await i18next.init(() => ({ await i18next.init({
lng: 'nl', lng: 'nl',
resources, resources,
})); });
export default i18next; export default i18next;
+1 -1
View File
@@ -16,5 +16,5 @@ import history from '@/routes/history';
</li> </li>
</ul> </ul>
</nav> </nav>
<div class="absolute top-0 left-64 p-4"><slot /></div> <div class="absolute top-0 right-0 bottom-0 left-64 p-4"><slot /></div>
</template> </template>