UserDashboard/components/dashboards/EnergyLineChart.tsx

167 lines
4.6 KiB
TypeScript

import React, { useRef, useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import ChartJS from 'chart.js/auto';
import zoomPlugin from 'chartjs-plugin-zoom';
ChartJS.register(zoomPlugin);
interface TimeSeriesEntry {
time: string;
value: number;
}
interface EnergyLineChartProps {
consumption: TimeSeriesEntry[];
generation: TimeSeriesEntry[];
}
const EnergyLineChart = ({ consumption, generation }: EnergyLineChartProps) => {
const chartRef = useRef<any>(null);
// Generate sorted unique time labels from both series
const allTimes = Array.from(new Set([
...consumption.map(d => d.time),
...generation.map(d => d.time),
])).sort(); // e.g., ["00:00", "00:30", "01:00", ...]
// Map times to values
const consumptionMap = Object.fromEntries(consumption.map(d => [d.time, d.value]));
const generationMap = Object.fromEntries(generation.map(d => [d.time, d.value]));
const [startIndex, setStartIndex] = useState(0);
const [endIndex, setEndIndex] = useState(allTimes.length - 1);
useEffect(() => {
if (typeof window !== 'undefined') {
import('hammerjs');
}
}, []);
const filteredLabels = allTimes.slice(startIndex, endIndex + 1);
const filteredConsumption = filteredLabels.map(t => consumptionMap[t] ?? null);
const filteredGeneration = filteredLabels.map(t => generationMap[t] ?? null);
const allValues = [...filteredConsumption, ...filteredGeneration].filter(v => v !== null) as number[];
const maxValue = allValues.length > 0 ? Math.max(...allValues) : 0;
const yAxisSuggestedMax = maxValue * 1.15;
const data = {
labels: filteredLabels,
datasets: [
{
label: 'Consumption',
data: filteredConsumption,
borderColor: '#8884d8',
tension: 0.4,
fill: false,
},
{
label: 'Generation',
data: filteredGeneration,
borderColor: '#82ca9d',
tension: 0.4,
fill: false,
},
],
};
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' as const },
zoom: {
zoom: {
wheel: { enabled: true },
pinch: { enabled: true },
mode: 'x' as const,
},
pan: { enabled: true, mode: 'x' as const },
},
tooltip: { enabled: true, mode: 'index' as const, intersect: false },
},
scales: {
x: {
title: {
display: true,
text: 'Time (HH:MM)',
font: {
weight: 'bold' as const, // ✅ FIX: cast as 'const'
},
},
},
y: {
beginAtZero: true,
suggestedMax: yAxisSuggestedMax,
title: {
display: true,
text: 'Power (kW)',
font: {
weight: 'bold' as const, // ✅ FIX: cast as 'const'
},
},
},
},
} as const; // ✅ Ensures compatibility with chart.js types
const handleResetZoom = () => {
chartRef.current?.resetZoom();
};
return (
<div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light">
<div className="h-98 w-full">
<div className="flex justify-between items-center mb-2">
<h2 className="text-lg font-bold dark:text-white-light">Energy Consumption & Generation</h2>
<button onClick={handleResetZoom} className="btn-primary px-8 py-2 text-sm">
Reset
</button>
</div>
{/* Time range selectors */}
<div className="mb-4 flex gap-4 items-center">
<label className='font-medium'>
From:{' '}
<select
value={startIndex}
onChange={(e) => {
const val = Number(e.target.value);
setStartIndex(val <= endIndex ? val : endIndex);
}}
className="border rounded p-1"
>
{allTimes.map((label, idx) => (
<option key={idx} value={idx}>{label}</option>
))}
</select>
</label>
<label className='font-medium'>
To:{' '}
<select
value={endIndex}
onChange={(e) => {
const val = Number(e.target.value);
setEndIndex(val >= startIndex ? val : startIndex);
}}
className="border rounded p-1"
>
{allTimes.map((label, idx) => (
<option key={idx} value={idx}>{label}</option>
))}
</select>
</label>
</div>
<div className="h-96 w-full">
<Line ref={chartRef} data={data} options={options} />
</div>
</div>
</div>
);
};
export default EnergyLineChart;