diff --git a/components/graphs/LineChart.tsx b/components/graphs/LineChart.tsx index 82b266c..550be6c 100644 --- a/components/graphs/LineChart.tsx +++ b/components/graphs/LineChart.tsx @@ -1,6 +1,7 @@ 'use client'; -import React, { useState } from 'react'; +import { useState, useMemo } from 'react'; import { Line } from 'react-chartjs-2'; +import useSWR from 'swr'; import { Chart as ChartJS, CategoryScale, @@ -9,7 +10,8 @@ import { LineElement, Title, Tooltip, - Legend, ChartOptions, + Legend, + ChartOptions, } from 'chart.js'; import { GraphType } from '@/lib/types'; @@ -34,13 +36,23 @@ const timeRanges = { interface Props { onlineFondet: GraphType[]; - osebx: GraphType[]; } -const LineChart = ({ onlineFondet, osebx }: Props) => { +interface OsebxResponse { + data: GraphType[]; +} + +const fetcher = (url: string) => fetch(url).then((res) => res.json()); + +const LineChart = ({ onlineFondet }: Props) => { const [selectedRange, setSelectedRange] = useState('5 år'); + const { data: osebxData, error: osebxError } = useSWR( + '/api/osebx', + fetcher, + ); + const filterDataByRange = (data: GraphType[], rangeDays: number) => { const today = new Date(); const cutoffDate = new Date(today); @@ -63,66 +75,96 @@ const LineChart = ({ onlineFondet, osebx }: Props) => { })); }; - const filteredOnlineFondet = filterDataByRange( - onlineFondet, - timeRanges[selectedRange], + const filteredOnlineFondet = useMemo( + () => filterDataByRange(onlineFondet, timeRanges[selectedRange]), + [onlineFondet, selectedRange], + ); + + const filteredOsebx = useMemo( + () => + osebxData + ? filterDataByRange(osebxData.data, timeRanges[selectedRange]) + : [], + [osebxData, selectedRange], + ); + + const normalizedOnlineFondet = useMemo( + () => normalizeData(filteredOnlineFondet), + [filteredOnlineFondet], + ); + + const normalizedOsebx = useMemo( + () => normalizeData(filteredOsebx), + [filteredOsebx], + ); + + const allDatesSet = useMemo( + () => + new Set([ + ...normalizedOnlineFondet.map((item) => item.date), + ...normalizedOsebx.map((item) => item.date), + ]), + [normalizedOnlineFondet, normalizedOsebx], ); - const filteredOsebx = filterDataByRange(osebx, timeRanges[selectedRange]); - - const normalizedOnlineFondet = normalizeData(filteredOnlineFondet); - const normalizedOsebx = normalizeData(filteredOsebx); - - const allDatesSet = new Set([ - ...normalizedOnlineFondet.map((item) => item.date), - ...normalizedOsebx.map((item) => item.date), - ]); - const allDates = Array.from(allDatesSet) - .map((date) => new Date(date)) - .sort((a, b) => a.getTime() - b.getTime()) - .map((date) => date.toISOString().split('T')[0]); - - const mergedData = allDates.map((date) => { - const onlineFondetItem = normalizedOnlineFondet.find( - (item) => (item.date as unknown as string).split('T')[0] === date, - ); - const osebxItem = normalizedOsebx.find( - (item) => (item.date as unknown as string).split('T')[0] === date, - ); - return { - date, - onlineFondetValue: onlineFondetItem ? onlineFondetItem.value : null, - osebxValue: osebxItem ? osebxItem.value : null, - }; - }); - - const data = { - labels: mergedData.map((item) => { - const date = new Date(item.date); - return date.toLocaleDateString('nb-NO', { - year: 'numeric', - month: 'short', - }); + const allDates = useMemo( + () => + Array.from(allDatesSet) + .map((date) => new Date(date)) + .sort((a, b) => a.getTime() - b.getTime()) + .map((date) => date.toISOString().split('T')[0]), + [allDatesSet], + ); + + const mergedData = useMemo( + () => + allDates.map((date) => { + const onlineFondetItem = normalizedOnlineFondet.find( + (item) => new Date(item.date).toISOString().split('T')[0] === date, + ); + const osebxItem = normalizedOsebx.find( + (item) => new Date(item.date).toISOString().split('T')[0] === date, + ); + + return { + date, + onlineFondetValue: onlineFondetItem ? onlineFondetItem.value : null, + osebxValue: osebxItem ? osebxItem.value : null, + }; + }), + [allDates, normalizedOnlineFondet, normalizedOsebx], + ); + + const chartData = useMemo( + () => ({ + labels: mergedData.map((item) => { + const date = new Date(item.date); + return date.toLocaleDateString('nb-NO', { + year: 'numeric', + month: 'short', + }); + }), + datasets: [ + { + label: 'Onlinefondet', + data: mergedData.map((item) => item.onlineFondetValue), + borderColor: 'rgba(75, 192, 192, 1)', + backgroundColor: 'rgba(75, 192, 192, 0.2)', + fill: true, + spanGaps: true, + }, + { + label: 'OSEBX', + data: mergedData.map((item) => item.osebxValue), + borderColor: 'rgba(255, 99, 132, 1)', + backgroundColor: 'rgba(255, 99, 132, 0.2)', + fill: true, + spanGaps: true, + }, + ], }), - datasets: [ - { - label: 'Onlinefondet', - data: mergedData.map((item) => item.onlineFondetValue), - borderColor: 'rgba(75, 192, 192, 1)', - backgroundColor: 'rgba(75, 192, 192, 0.2)', - fill: true, - spanGaps: true, - }, - { - label: 'OSEBX', - data: mergedData.map((item) => item.osebxValue), - borderColor: 'rgba(255, 99, 132, 1)', - backgroundColor: 'rgba(255, 99, 132, 0.2)', - fill: true, - spanGaps: true, - }, - ], - }; + [mergedData], + ); const options: ChartOptions<'line'> = { responsive: true, @@ -132,8 +174,8 @@ const LineChart = ({ onlineFondet, osebx }: Props) => { }, tooltip: { callbacks: { - label: (ctx) => `${Math.round(ctx.raw as number * 100) / 100}%` - } + label: (ctx) => `${Math.round((ctx.raw as number) * 100) / 100}%`, + }, }, }, scales: { @@ -154,10 +196,24 @@ const LineChart = ({ onlineFondet, osebx }: Props) => { }, }; + if (osebxError) { + return
Noe gikk galt: {osebxError.message}
; + } + + if (!osebxData) { + return ( +
+
+
+
+
+ ); + } + return (
- +
diff --git a/components/graphs/PerformanceDisplay.tsx b/components/graphs/PerformanceDisplay.tsx index da1e8b0..3a4363c 100644 --- a/components/graphs/PerformanceDisplay.tsx +++ b/components/graphs/PerformanceDisplay.tsx @@ -1,28 +1,27 @@ -'use client'; -import useSWR from 'swr'; import ErrorPage from '../all/Error'; import Table from '../form/Table'; import LineChart from './LineChart'; import PieChart from './PieChart'; +import { prisma } from '@/lib/prisma'; import { CompositionType } from '@/lib/types'; -const fetcher = (url: string) => fetch(url).then((res) => res.json()); - -const PerformanceDisplay = () => { - const { data: osebxData, error: osebxError } = useSWR('/api/osebx', fetcher); - const { data: compositionData, error: compositionError } = useSWR( - '/api/admin/composition', - fetcher, - ); +const PerformanceDisplay = async () => { + const compositionData = await prisma.composition.findMany({ + orderBy: { + date: 'desc', + }, + }); - const { data: onlineFondetData, error: onlineFondetError } = useSWR( - '/api/admin/portfolio', - fetcher, - ); + const onlineFondetData = await prisma.performance.findMany({ + orderBy: { + date: 'desc', + }, + }); - if (osebxError || compositionError || onlineFondetError) - return ; + if (!compositionData || !onlineFondetData) { + return ; + } const columns = [ { @@ -46,7 +45,7 @@ const PerformanceDisplay = () => { Denne smultringen gir en oversikt over fondets sammensetning
{compositionData ? ( - + ) : (
@@ -58,10 +57,7 @@ const PerformanceDisplay = () => { Fondets prestasjon over tid
{onlineFondetData ? ( - + ) : (
@@ -76,7 +72,7 @@ const PerformanceDisplay = () => { Tabellen viser fond, andel og kategori
{compositionData ? ( - +
) : (