UserDashboard/components/dashboards/MonthlyBarChart.tsx
2025-08-12 15:49:20 +08:00

200 lines
6.1 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Legend,
} from 'recharts';
import { format } from 'date-fns';
import { fetchPowerTimeseries } from '@/app/utils/api';
interface MonthlyBarChartProps {
siteId: string;
}
interface TimeSeriesEntry {
time: string;
value: number;
}
const groupTimeSeries = (
data: TimeSeriesEntry[],
mode: 'monthly'
): TimeSeriesEntry[] => {
const groupMap = new Map<string, number[]>();
for (const entry of data) {
const date = new Date(entry.time);
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
if (!groupMap.has(key)) groupMap.set(key, []);
groupMap.get(key)!.push(entry.value);
}
return Array.from(groupMap.entries()).map(([time, values]) => ({
time,
value: values.reduce((sum, v) => sum + v, 0),
}));
};
const MonthlyBarChart: React.FC<MonthlyBarChartProps> = ({ siteId }) => {
const [chartData, setChartData] = useState<
{ month: string; consumption: number; generation: number }[]
>([]);
const [loading, setLoading] = useState(true);
function useIsDarkMode() {
const [isDark, setIsDark] = useState(() =>
typeof document !== 'undefined'
? document.body.classList.contains('dark')
: false
);
useEffect(() => {
const check = () => setIsDark(document.body.classList.contains('dark'));
check();
// Listen for class changes on <body>
const observer = new MutationObserver(check);
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}, []);
return isDark;
}
const isDark = useIsDarkMode();
const consumptionColor = isDark ? '#ba8e23' : '#003049';
const generationColor = isDark ? '#fcd913' : '#669bbc';
useEffect(() => {
if (!siteId) return;
const fetchMonthlyData = async () => {
setLoading(true);
const start = '2025-01-01T00:00:00+08:00';
const end = '2025-12-31T23:59:59+08:00';
try {
const res = await fetchPowerTimeseries(siteId, start, end);
const groupedConsumption = groupTimeSeries(res.consumption, 'monthly');
const groupedGeneration = groupTimeSeries(res.generation, 'monthly');
const monthMap = new Map<string, { consumption: number; generation: number }>();
for (const entry of groupedConsumption) {
if (!monthMap.has(entry.time)) {
monthMap.set(entry.time, { consumption: 0, generation: 0 });
}
monthMap.get(entry.time)!.consumption = entry.value;
}
for (const entry of groupedGeneration) {
if (!monthMap.has(entry.time)) {
monthMap.set(entry.time, { consumption: 0, generation: 0 });
}
monthMap.get(entry.time)!.generation = entry.value;
}
const formatted = Array.from(monthMap.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, val]) => ({
month: format(new Date(`${key}-01`), 'MMM'),
consumption: val.consumption,
generation: val.generation,
}));
setChartData(formatted.slice(-6)); // last 6 months
} catch (error) {
console.error('Failed to fetch monthly power data:', error);
setChartData([]);
} finally {
setLoading(false);
}
};
fetchMonthlyData();
}, [siteId]);
if (loading || !siteId || chartData.length === 0) {
return (
<div className="bg-white p-3 rounded-lg shadow-md dark:bg-rtgray-800 dark:text-white-light">
<div className="flex justify-between items-center mb-2">
<h2 className="text-lg font-bold">Monthly Energy Yield</h2>
</div>
<div className="h-96 w-full flex items-center justify-center">
<p className="text-white/70">
{loading ? 'Loading data...' : 'No data available for chart.'}
</p>
</div>
</div>
);
}
return (
<div className="bg-white p-3 rounded-lg dark:bg-rtgray-800 dark:text-white-light">
<div className="h-[200px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData} >
<XAxis
dataKey="month"
tick={{ fontSize: 10, fill: isDark ? '#fff' : '#222' }}
axisLine={{ stroke: isDark ? '#fff' : '#222' }}
tickLine={{ stroke: isDark ? '#fff' : '#222' }}
/>
<YAxis
tick={{ fontSize: 10, fill: isDark ? '#fff' : '#222' }}
axisLine={{ stroke: isDark ? '#fff' : '#222' }}
tickLine={{ stroke: isDark ? '#fff' : '#222' }}
label={{
value: 'Power (kW)', // <-- Y-axis label
angle: -90, // Vertical text
position: 'insideLeft', // Position inside the chart area
style: {
textAnchor: 'middle',
fill: isDark ? '#fff' : '#222',
fontSize: 12,
},
}}
/>
<Tooltip
formatter={(value: number) => [`${value.toFixed(2)} kWh`]}
labelFormatter={(label) => `${label}`}
contentStyle={{
background: isDark ? '#232b3e' : '#fff',
color: isDark ? '#fff' : '#222',
border: isDark ? '1px solid #444' : '1px solid #ccc',
}}
labelStyle={{
color: isDark ? '#fff' : '#222',
}}
cursor={{
fill: isDark ? '#808080' : '#e0e7ef', // dark mode bg, light mode bg
fillOpacity: isDark ? 0.6 : 0.3, // adjust opacity as you like
}}
/>
<Legend
wrapperStyle={{
color: isDark ? '#fff' : '#222',
}}
/>
<Bar dataKey="consumption" fill={consumptionColor} name="Consumption (kWh)" />
<Bar dataKey="generation" fill={generationColor} name="Generation (kWh)" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
);
};
export default MonthlyBarChart;