|
1 | 1 | import { Skeleton, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
| 2 | +import dynamic from 'next/dynamic'; |
2 | 3 | import React, { useEffect, useState } from 'react';
|
3 |
| -import Chart from 'react-apexcharts'; |
4 | 4 |
|
5 |
| -import { |
6 |
| - type SearchQuery, |
7 |
| - searchQueryLabel, |
8 |
| -} from '@/modules/SearchQuery/SearchQuery'; |
9 | 5 | import type { GenericFetchedData } from '@/pages/dashboard/index';
|
10 | 6 |
|
| 7 | +// Dynamically import react-apexcharts with SSR disabled. |
| 8 | +const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); |
| 9 | + |
11 | 10 | export type GPATrendType = {
|
12 | 11 | season: string[];
|
13 | 12 | years: string[];
|
14 | 13 | gpa: number[];
|
15 | 14 | };
|
16 | 15 |
|
17 | 16 | type Props = {
|
18 |
| - recentSemesters: SearchQuery; |
19 |
| - gpaTrend: GenericFetchedData<GPATrendType>; |
| 17 | + gpaTrend?: GenericFetchedData<GPATrendType>; |
| 18 | + chartTitle?: string; |
| 19 | + xAxisLabels?: string[]; |
| 20 | + yAxisFormatter?: (value: number) => string; |
| 21 | + tooltipFormatter?: (value: number) => string; |
| 22 | + series?: { |
| 23 | + name: string; |
| 24 | + data: number[]; |
| 25 | + }[]; |
20 | 26 | };
|
21 | 27 |
|
22 |
| -function LineGraph({ recentSemesters, gpaTrend }: Props) { |
| 28 | +function LineGraph({ gpaTrend, chartTitle, xAxisLabels, yAxisFormatter, tooltipFormatter, series }: Props): React.ReactNode { |
23 | 29 | const [chartType, setChartType] = useState<'line' | 'bar'>('line');
|
24 | 30 | const [chartData, setChartData] = useState<{
|
25 | 31 | options: {
|
@@ -77,7 +83,8 @@ function LineGraph({ recentSemesters, gpaTrend }: Props) {
|
77 | 83 | },
|
78 | 84 | },
|
79 | 85 | xaxis: {
|
80 |
| - categories: [] as string[], |
| 86 | + categories: [], |
| 87 | + |
81 | 88 | labels: {
|
82 | 89 | rotate: -45,
|
83 | 90 | },
|
@@ -110,63 +117,80 @@ function LineGraph({ recentSemesters, gpaTrend }: Props) {
|
110 | 117 | });
|
111 | 118 |
|
112 | 119 | useEffect(() => {
|
113 |
| - if (gpaTrend.state === 'done') { |
114 |
| - setChartData({ |
| 120 | + if (gpaTrend && gpaTrend.state === 'done') { |
| 121 | + setChartData(prev => ({ |
115 | 122 | options: {
|
116 |
| - ...chartData.options, |
| 123 | + ...prev.options, |
| 124 | + |
117 | 125 | xaxis: {
|
118 | 126 | categories: gpaTrend.data.season,
|
119 | 127 | labels: { rotate: -45 },
|
120 | 128 | },
|
121 | 129 | },
|
122 | 130 | series: [
|
123 | 131 | {
|
124 |
| - name: searchQueryLabel(recentSemesters), |
| 132 | + name: chartTitle || '', |
| 133 | + |
125 | 134 | data: gpaTrend.data.gpa,
|
126 | 135 | },
|
127 | 136 | ],
|
128 |
| - }); |
129 |
| - } |
130 |
| - }, [gpaTrend, recentSemesters]); |
131 |
| - |
132 |
| - const handleChartToggle = ( |
133 |
| - _event: React.MouseEvent<HTMLElement>, |
134 |
| - newChartType: 'line' | 'bar' | null, |
135 |
| - ) => { |
136 |
| - if (newChartType) { |
137 |
| - setChartType(newChartType); |
| 137 | + })); |
| 138 | + } else if (series) { |
| 139 | + setChartData(prev => ({ |
| 140 | + options: { |
| 141 | + ...prev.options, |
| 142 | + |
| 143 | + xaxis: { |
| 144 | + categories: xAxisLabels || [], |
| 145 | + labels: { rotate: -45 }, |
| 146 | + }, |
| 147 | + |
| 148 | + yaxis: { |
| 149 | + ...prev.options.yaxis, |
| 150 | + labels: { |
| 151 | + formatter: |
| 152 | + yAxisFormatter || ((value: number) => value.toFixed(2)), |
| 153 | + }, |
| 154 | + }, |
| 155 | + |
| 156 | + tooltip: { |
| 157 | + y: { |
| 158 | + formatter: |
| 159 | + tooltipFormatter || ((value: number) => value.toFixed(3)), |
| 160 | + }, |
| 161 | + }, |
| 162 | + }, |
| 163 | + |
| 164 | + series: series, |
| 165 | + })); |
138 | 166 | }
|
139 |
| - }; |
| 167 | + }, [gpaTrend, series, xAxisLabels, yAxisFormatter, tooltipFormatter]); |
140 | 168 |
|
141 |
| - if (typeof gpaTrend === 'undefined' || gpaTrend.state === 'error') { |
142 |
| - return null; |
143 |
| - } |
144 |
| - if (gpaTrend.state === 'loading') { |
145 |
| - return ( |
146 |
| - <div className="p-2"> |
147 |
| - <Skeleton variant="rounded" className="w-full h-60 m-2" /> |
148 |
| - </div> |
149 |
| - ); |
| 169 | + if (gpaTrend) { |
| 170 | + if (gpaTrend.state === 'error') { |
| 171 | + return null; |
| 172 | + } |
| 173 | + if (gpaTrend.state === 'loading') { |
| 174 | + return ( |
| 175 | + <div className="p-2"> |
| 176 | + <Skeleton variant="rounded" className="w-full h-60 m-2" /> |
| 177 | + </div> |
| 178 | + ); |
| 179 | + } |
150 | 180 | }
|
151 | 181 |
|
| 182 | + const handleChartToggle = (_event: React.MouseEvent<HTMLElement>, newChartType: 'line' | 'bar' | null) => { |
| 183 | + if (newChartType) setChartType(newChartType); |
| 184 | + }; |
| 185 | + |
152 | 186 | return (
|
153 | 187 | <div className="p-2">
|
154 |
| - <ToggleButtonGroup |
155 |
| - value={chartType} |
156 |
| - exclusive |
157 |
| - onChange={handleChartToggle} |
158 |
| - className="mb-2" |
159 |
| - > |
160 |
| - <ToggleButton value="bar"></ToggleButton> |
161 |
| - <ToggleButton value="line"></ToggleButton> |
| 188 | + <ToggleButtonGroup value={chartType} exclusive onChange={handleChartToggle} className="mb-2"> |
| 189 | + <ToggleButton value="bar" /> |
| 190 | + <ToggleButton value="line" /> |
162 | 191 | </ToggleButtonGroup>
|
163 | 192 | <div className="h-64">
|
164 |
| - <Chart |
165 |
| - options={chartData.options} |
166 |
| - series={chartData.series} |
167 |
| - type={chartType} |
168 |
| - height={250} |
169 |
| - /> |
| 193 | + <Chart options={chartData.options} series={chartData.series} type={chartType} height={250} /> |
170 | 194 | </div>
|
171 | 195 | </div>
|
172 | 196 | );
|
|
0 commit comments