import React, { useEffect, useState } from 'react'; import { Typography, Spin, Message, Card, Grid } from '@arco-design/web-react'; import '@arco-design/web-react/dist/css/arco.css'; import { initVChartArcoTheme } from '@visactor/vchart-arco-theme'; import { VChart } from '@visactor/react-vchart'; import API_BASE_URL from './config'; const { Row, Col } = Grid; function MarketDataPage() { const [fiveMinData, setFiveMinData] = useState([]); const [dayAheadData, setDayAheadData] = useState([]); const [loading, setLoading] = useState(true); const [fiveMinLastUpdated, setFiveMinLastUpdated] = useState(null); const [dayAheadLastUpdated, setDayAheadLastUpdated] = useState(null); const [now, setNow] = useState(new Date()); useEffect(() => { const fetchFiveMinData = async () => { try { const res = await fetch(`${API_BASE_URL}/market/real-time`); if (!res.ok) throw new Error('Failed to fetch real-time data'); const json = await res.json(); setFiveMinData(json); setFiveMinLastUpdated(new Date()); } catch (err) { console.error(err); Message.error('Failed to load real-time market data.'); } }; const fetchDayAheadData = async () => { try { const res = await fetch(`${API_BASE_URL}/market/day-ahead`); if (!res.ok) throw new Error('Failed to fetch day-ahead data'); const json = await res.json(); setDayAheadData(json); setDayAheadLastUpdated(new Date()); } catch (err) { console.error(err); Message.error('Failed to load day-ahead market data.'); } }; const initialize = async () => { setLoading(true); await Promise.all([fetchFiveMinData(), fetchDayAheadData()]); setLoading(false); initVChartArcoTheme({ defaultMode: 'light', isWatchingMode: true }); }; initialize(); const fiveMinInterval = setInterval(fetchFiveMinData, 5 * 60 * 1000); // 5 minutes const dayAheadInterval = setInterval(fetchDayAheadData, 60 * 60 * 1000); // 1 hour const timer = setInterval(() => { setNow(new Date()); }, 1000); return () => { clearInterval(fiveMinInterval); clearInterval(dayAheadInterval); clearInterval(timer); }; }, []); const getChartSpec = (data, title) => ({ type: 'line', data: { values: data.map(d => ({ timestamp: new Date(d.timestamp).toLocaleString(), LMP: d.lmp, Energy: d.energy, Congestion: d.congestion, Loss: d.loss })), transforms: [ { type: 'fold', options: { key: 'name', value: 'value', fields: ['LMP', 'Energy', 'Congestion', 'Loss'] } } ] }, xField: 'timestamp', yField: 'value', seriesField: 'name', smooth: true, legend: { position: 'top' }, tooltip: { formatter: (datum) => ({ name: datum.name, value: datum.value.toFixed(2) }) }, dataZoom: [ { orient: 'bottom', height: 20, start: title === 'fiveMin' ? 0.9 : 0.05, end: 1 } ] }); const computeKPIs = (data) => { if (!data.length) return { avgLmp: 0, totalEnergy: 0, maxCongestion: 0, avgLoss: 0 }; const avgLmp = data.reduce((sum, d) => sum + d.lmp, 0) / data.length; const totalEnergy = data.reduce((sum, d) => sum + d.energy, 0); const maxCongestion = Math.max(...data.map(d => d.congestion)); const avgLoss = data.reduce((sum, d) => sum + d.loss, 0) / data.length; return { avgLmp, totalEnergy, maxCongestion, avgLoss }; }; const fiveMinKPIs = computeKPIs(fiveMinData); const dayAheadKPIs = computeKPIs(dayAheadData); const formatTimeAgo = (timestamp) => { if (!timestamp) return ''; const seconds = Math.floor((now - timestamp) / 1000); if (seconds < 60) return `${seconds} sec ago`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes} min ago`; const hours = Math.floor(minutes / 60); return `${hours} hr ago`; }; return (