fixed monthly bar chart, chnage color scheme
This commit is contained in:
parent
29509f5bd1
commit
62252a1689
@ -1,8 +1,7 @@
|
|||||||
// app/adminDashboard/page.tsx
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
import SiteSelector from '@/components/dashboards/SiteSelector';
|
import SiteSelector from '@/components/dashboards/SiteSelector';
|
||||||
import SiteStatus from '@/components/dashboards/SiteStatus';
|
import SiteStatus from '@/components/dashboards/SiteStatus';
|
||||||
@ -14,24 +13,38 @@ import DashboardLayout from './dashlayout';
|
|||||||
import { SiteName, SiteDetails, mockSiteData } from '@/types/SiteData';
|
import { SiteName, SiteDetails, mockSiteData } from '@/types/SiteData';
|
||||||
|
|
||||||
const AdminDashboard = () => {
|
const AdminDashboard = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const siteParam = searchParams?.get('site');
|
const siteParam = searchParams?.get('site');
|
||||||
|
const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
|
||||||
|
|
||||||
const [selectedSite, setSelectedSite] = useState<SiteName>(() => {
|
const [selectedSite, setSelectedSite] = useState<SiteName>(() => {
|
||||||
const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
|
|
||||||
if (siteParam && validSiteNames.includes(siteParam as SiteName)) {
|
if (siteParam && validSiteNames.includes(siteParam as SiteName)) {
|
||||||
return siteParam as SiteName;
|
return siteParam as SiteName;
|
||||||
}
|
}
|
||||||
return 'Site A';
|
return 'Site A';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keep siteParam and selectedSite in sync
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
|
if (
|
||||||
if (siteParam && validSiteNames.includes(siteParam as SiteName) && siteParam !== selectedSite) {
|
siteParam &&
|
||||||
|
validSiteNames.includes(siteParam as SiteName) &&
|
||||||
|
siteParam !== selectedSite
|
||||||
|
) {
|
||||||
setSelectedSite(siteParam as SiteName);
|
setSelectedSite(siteParam as SiteName);
|
||||||
}
|
}
|
||||||
}, [siteParam, selectedSite]);
|
}, [siteParam, selectedSite]);
|
||||||
|
|
||||||
|
// Update query string when site is changed manually
|
||||||
|
const handleSiteChange = (newSite: SiteName) => {
|
||||||
|
setSelectedSite(newSite);
|
||||||
|
const newUrl = `${pathname}?site=${encodeURIComponent(newSite)}`;
|
||||||
|
router.push(newUrl);
|
||||||
|
};
|
||||||
|
|
||||||
const currentSiteDetails: SiteDetails = mockSiteData[selectedSite] || {
|
const currentSiteDetails: SiteDetails = mockSiteData[selectedSite] || {
|
||||||
location: 'N/A',
|
location: 'N/A',
|
||||||
inverterProvider: 'N/A',
|
inverterProvider: 'N/A',
|
||||||
@ -39,11 +52,11 @@ const AdminDashboard = () => {
|
|||||||
lastSyncTimestamp: 'N/A',
|
lastSyncTimestamp: 'N/A',
|
||||||
consumptionData: [],
|
consumptionData: [],
|
||||||
generationData: [],
|
generationData: [],
|
||||||
systemStatus: 'N/A', // Fallback
|
systemStatus: 'N/A',
|
||||||
temperature: 'N/A', // Fallback
|
temperature: 'N/A',
|
||||||
solarPower: 0, // Fallback
|
solarPower: 0,
|
||||||
realTimePower: 0, // Fallback
|
realTimePower: 0,
|
||||||
installedPower: 0, // Fallback
|
installedPower: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCSVExport = () => {
|
const handleCSVExport = () => {
|
||||||
@ -57,13 +70,14 @@ const AdminDashboard = () => {
|
|||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="px-6 space-y-6">
|
<div className="px-6 space-y-6">
|
||||||
<h1 className='text-lg font-semibold'>Admin Dashboard</h1>
|
<h1 className="text-lg font-semibold">Admin Dashboard</h1>
|
||||||
{/* Top Section: Site Selector, Site Status, and KPI Table */}
|
|
||||||
{/* This grid will now properly arrange them into two columns on md screens and up */}
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
{/* First Column: Site Selector and Site Status */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SiteSelector selectedSite={selectedSite} setSelectedSite={setSelectedSite} />
|
<SiteSelector
|
||||||
|
selectedSite={selectedSite}
|
||||||
|
setSelectedSite={handleSiteChange}
|
||||||
|
/>
|
||||||
<SiteStatus
|
<SiteStatus
|
||||||
selectedSite={selectedSite}
|
selectedSite={selectedSite}
|
||||||
location={currentSiteDetails.location}
|
location={currentSiteDetails.location}
|
||||||
@ -72,15 +86,12 @@ const AdminDashboard = () => {
|
|||||||
lastSyncTimestamp={currentSiteDetails.lastSyncTimestamp}
|
lastSyncTimestamp={currentSiteDetails.lastSyncTimestamp}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Second Column: KPI Table */}
|
|
||||||
<div> {/* This div will now be the second column */}
|
<div>
|
||||||
<KPI_Table siteData={currentSiteDetails} />
|
<KPI_Table siteData={currentSiteDetails} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Charts Section */}
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div className="pb-5">
|
<div className="pb-5">
|
||||||
<EnergyLineChart
|
<EnergyLineChart
|
||||||
@ -93,7 +104,7 @@ const AdminDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row gap-4 justify-center"> {/* Added a div wrapper */}
|
<div className="flex flex-col md:flex-row gap-4 justify-center">
|
||||||
<button onClick={handleCSVExport} className="text-sm lg:text-lg btn-primary">
|
<button onClick={handleCSVExport} className="text-sm lg:text-lg btn-primary">
|
||||||
Export Raw Data to CSV
|
Export Raw Data to CSV
|
||||||
</button>
|
</button>
|
||||||
@ -108,3 +119,4 @@ const AdminDashboard = () => {
|
|||||||
|
|
||||||
export default AdminDashboard;
|
export default AdminDashboard;
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const LoginPage = (props: Props) => {
|
|||||||
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
|
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex min-h-screen items-center justify-center bg-[url(/assets/images/auth/map.png)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
|
<div className="relative flex min-h-screen items-center justify-center bg-[linear-gradient(45deg,#ffffff_0%,#fcd913_100%)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
|
||||||
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
|
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
|
||||||
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
|
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
|
||||||
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
|
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
|
||||||
|
@ -12,7 +12,7 @@ const RegisterPage = (props: Props) => {
|
|||||||
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
|
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex min-h-screen items-center justify-center bg-[url(/assets/images/auth/map.png)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
|
<div className="relative flex min-h-screen items-center justify-center bg-[linear-gradient(45deg,#ffffff_0%,#fcd913_100%)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
|
||||||
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
|
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
|
||||||
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
|
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
|
||||||
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
|
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import { Metadata } from 'next';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
const Sales = () => {
|
const Sales = () => {
|
||||||
const [selectedSite, setSelectedSite] = useState('');
|
const [selectedSite, setSelectedSite] = useState('');
|
||||||
const sites = ['Site A', 'Site B', 'Site C']; // replace with your actual site list
|
const sites = ['Site A', 'Site B', 'Site C'];
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleGoToDashboard = () => {
|
||||||
|
if (selectedSite) {
|
||||||
|
router.push(`/adminDashboard?site=${encodeURIComponent(selectedSite)}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col items-center justify-center p-4 bg-gray-50">
|
<div className="min-h-screen flex flex-col items-center justify-center p-4 bg-gray-50">
|
||||||
@ -31,12 +37,17 @@ const Sales = () => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
{selectedSite && (
|
{selectedSite && (
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<p className="mt-4 text-green-700">You selected: {selectedSite}</p>
|
<p className="mt-4 text-green-700">You selected: {selectedSite}</p>
|
||||||
<button className="btn-primary">Go to Dashboard</button>
|
<button
|
||||||
|
onClick={handleGoToDashboard}
|
||||||
|
className="bg-yellow-400 hover:bg-yellow-500 text-white font-semibold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
Go to Dashboard
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -45,3 +56,4 @@ const Sales = () => {
|
|||||||
|
|
||||||
export default Sales;
|
export default Sales;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,31 +1,10 @@
|
|||||||
// components/dashboards/EnergyLineChart.tsx
|
import React, { useRef, useEffect, useState } from 'react';
|
||||||
'use client';
|
|
||||||
import { useRef, useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
Chart as ChartJS,
|
|
||||||
LineElement,
|
|
||||||
PointElement,
|
|
||||||
LinearScale,
|
|
||||||
CategoryScale,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
} from 'chart.js';
|
|
||||||
import zoomPlugin from 'chartjs-plugin-zoom';
|
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line } from 'react-chartjs-2';
|
||||||
|
import ChartJS from 'chart.js/auto';
|
||||||
|
import zoomPlugin from 'chartjs-plugin-zoom';
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(zoomPlugin);
|
||||||
LineElement,
|
|
||||||
PointElement,
|
|
||||||
LinearScale,
|
|
||||||
CategoryScale,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
zoomPlugin
|
|
||||||
);
|
|
||||||
|
|
||||||
// Define props interface for EnergyLineChart
|
|
||||||
interface EnergyLineChartProps {
|
interface EnergyLineChartProps {
|
||||||
consumptionData: number[];
|
consumptionData: number[];
|
||||||
generationData: number[];
|
generationData: number[];
|
||||||
@ -40,30 +19,38 @@ const labels = [
|
|||||||
|
|
||||||
const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartProps) => {
|
const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartProps) => {
|
||||||
const chartRef = useRef<any>(null);
|
const chartRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const [startIndex, setStartIndex] = useState(0);
|
||||||
|
const [endIndex, setEndIndex] = useState(labels.length - 1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
import('hammerjs');
|
import('hammerjs');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Calculate suggestedMax dynamically based on the current data
|
// Filter data arrays based on selected range
|
||||||
const allDataPoints = [...consumptionData, ...generationData];
|
const filteredConsumption = consumptionData.slice(startIndex, endIndex + 1);
|
||||||
const maxDataValue = allDataPoints.length > 0 ? Math.max(...allDataPoints) : 0; // Handle empty array
|
const filteredGeneration = generationData.slice(startIndex, endIndex + 1);
|
||||||
const yAxisSuggestedMax = maxDataValue * 1.15; // Adds 15% padding
|
const filteredLabels = labels.slice(startIndex, endIndex + 1);
|
||||||
|
|
||||||
|
const allDataPoints = [...filteredConsumption, ...filteredGeneration];
|
||||||
|
const maxDataValue = allDataPoints.length > 0 ? Math.max(...allDataPoints) : 0;
|
||||||
|
const yAxisSuggestedMax = maxDataValue * 1.15;
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels,
|
labels: filteredLabels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Consumption',
|
label: 'Consumption',
|
||||||
data: consumptionData, // Use prop data
|
data: filteredConsumption,
|
||||||
borderColor: '#8884d8',
|
borderColor: '#8884d8',
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
fill: false,
|
fill: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Generation',
|
label: 'Generation',
|
||||||
data: generationData, // Use prop data
|
data: filteredGeneration,
|
||||||
borderColor: '#82ca9d',
|
borderColor: '#82ca9d',
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
fill: false,
|
fill: false,
|
||||||
@ -75,35 +62,19 @@ const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartPro
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: { position: 'top' as const },
|
||||||
position: 'top' as const,
|
|
||||||
},
|
|
||||||
zoom: {
|
zoom: {
|
||||||
zoom: {
|
zoom: {
|
||||||
wheel: {
|
wheel: { enabled: true },
|
||||||
enabled: true,
|
pinch: { enabled: true },
|
||||||
|
mode: 'x' as const,
|
||||||
},
|
},
|
||||||
pinch: {
|
pan: { enabled: true, mode: 'x' as const },
|
||||||
enabled: true,
|
|
||||||
},
|
},
|
||||||
mode: "x" as const,
|
tooltip: { enabled: true, mode: 'index' as const, intersect: false },
|
||||||
},
|
|
||||||
pan: {
|
|
||||||
enabled: true,
|
|
||||||
mode: "x" as const,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
mode: 'index' as const,
|
|
||||||
intersect: false,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: { beginAtZero: true, suggestedMax: yAxisSuggestedMax },
|
||||||
beginAtZero: true,
|
|
||||||
suggestedMax: yAxisSuggestedMax, // Use the dynamically calculated value
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,13 +87,49 @@ const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartPro
|
|||||||
<div className="h-98 w-full">
|
<div className="h-98 w-full">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<h2 className="text-lg font-bold dark:text-white-light">Energy Consumption & Generation</h2>
|
<h2 className="text-lg font-bold dark:text-white-light">Energy Consumption & Generation</h2>
|
||||||
<button
|
<button onClick={handleResetZoom} className="btn-primary px-8 py-2 text-sm">
|
||||||
onClick={handleResetZoom}
|
|
||||||
className="btn-primary px-8 py-2 text-sm"
|
|
||||||
>
|
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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"
|
||||||
|
>
|
||||||
|
{labels.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"
|
||||||
|
>
|
||||||
|
{labels.map((label, idx) => (
|
||||||
|
<option key={idx} value={idx}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="h-96 w-full">
|
<div className="h-96 w-full">
|
||||||
<Line ref={chartRef} data={data} options={options} />
|
<Line ref={chartRef} data={data} options={options} />
|
||||||
</div>
|
</div>
|
||||||
@ -133,3 +140,4 @@ const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartPro
|
|||||||
|
|
||||||
export default EnergyLineChart;
|
export default EnergyLineChart;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// components/MonthlyBarChart.tsx
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
BarChart,
|
BarChart,
|
||||||
@ -7,42 +6,57 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Cell,
|
Legend,
|
||||||
Legend // Import Legend to distinguish between consumption and generation
|
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { SiteDetails } from '@/types/SiteData'; // Adjust import path as necessary
|
import { SiteDetails } from '@/types/SiteData';
|
||||||
|
|
||||||
interface MonthlyBarChartProps {
|
interface MonthlyBarChartProps {
|
||||||
siteData: SiteDetails | null; // Pass the selected site's data as a prop
|
siteData: SiteDetails | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define specific colors for consumption and generation
|
const consumptionColor = '#003049';
|
||||||
const consumptionColor = '#003049'; // Darker blue/grey for consumption
|
const generationColor = '#669bbc';
|
||||||
const generationColor = '#669bbc'; // Lighter blue for generation
|
|
||||||
|
// Month names for X-axis labels
|
||||||
|
const MONTHS = [
|
||||||
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
||||||
|
];
|
||||||
|
|
||||||
const MonthlyBarChart: React.FC<MonthlyBarChartProps> = ({ siteData }) => {
|
const MonthlyBarChart: React.FC<MonthlyBarChartProps> = ({ siteData }) => {
|
||||||
|
|
||||||
// Prepare data for the chart
|
|
||||||
const chartData = React.useMemo(() => {
|
const chartData = React.useMemo(() => {
|
||||||
if (!siteData || siteData.consumptionData.length === 0 || siteData.generationData.length === 0) {
|
if (
|
||||||
|
!siteData ||
|
||||||
|
siteData.consumptionData.length === 0 ||
|
||||||
|
siteData.generationData.length === 0
|
||||||
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming consumptionData and generationData are arrays of daily values
|
// Initialize totals
|
||||||
// We'll map them to an array suitable for Recharts
|
const monthlyData = Array.from({ length: 12 }, (_, month) => ({
|
||||||
const dataLength = Math.min(siteData.consumptionData.length, siteData.generationData.length);
|
month: MONTHS[month],
|
||||||
return Array.from({ length: dataLength }).map((_, index) => ({
|
consumption: 0,
|
||||||
day: `Day ${index + 1}`, // Label for X-axis
|
generation: 0,
|
||||||
consumption: siteData.consumptionData[index],
|
|
||||||
generation: siteData.generationData[index],
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Group daily data into months (assume data is in order from Jan 1 to Dec 31)
|
||||||
|
for (let i = 0; i < siteData.consumptionData.length; i++) {
|
||||||
|
const monthIndex = Math.floor(i / 30.42); // Rough approximation (or replace with actual dates if available)
|
||||||
|
if (monthIndex < 12) {
|
||||||
|
monthlyData[monthIndex].consumption += siteData.consumptionData[i];
|
||||||
|
monthlyData[monthIndex].generation += siteData.generationData[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monthlyData;
|
||||||
}, [siteData]);
|
}, [siteData]);
|
||||||
|
|
||||||
if (!siteData || chartData.length === 0) {
|
if (!siteData || chartData.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light">
|
<div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<h2 className="text-lg font-bold pb-3">Daily Energy Consumption & Generation</h2>
|
<h2 className="text-lg font-bold pb-3">Monthly Energy Yield</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-96 w-full flex items-center justify-center">
|
<div className="h-96 w-full flex items-center justify-center">
|
||||||
<p className="text-white/70">No data available for chart. Please select a site.</p>
|
<p className="text-white/70">No data available for chart. Please select a site.</p>
|
||||||
@ -53,31 +67,19 @@ const MonthlyBarChart: React.FC<MonthlyBarChartProps> = ({ siteData }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light">
|
<div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light">
|
||||||
{/* Chart Title and any other header elements */}
|
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<h2 className="text-lg font-bold pb-3">Daily Energy Consumption & Generation</h2>
|
<h2 className="text-lg font-bold pb-3">Monthly Energy Yield</h2>
|
||||||
{/* You could add buttons or other controls here if needed */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* This div now acts as the direct container for the chart */}
|
|
||||||
{/* It explicitly sets the height and width for the ResponsiveContainer */}
|
|
||||||
<div className="h-96 w-full">
|
<div className="h-96 w-full">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={chartData}>
|
<BarChart data={chartData}>
|
||||||
<XAxis dataKey="day" interval={chartData.length > 10 ? 'preserveStartEnd' : 0} /> {/* Adjust interval for readability */}
|
<XAxis dataKey="month" />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Legend /> {/* Add Legend for consumption and generation bars */}
|
<Legend />
|
||||||
|
<Bar dataKey="consumption" fill={consumptionColor} name="Consumption (kWh)" />
|
||||||
{/* Bar for Consumption */}
|
<Bar dataKey="generation" fill={generationColor} name="Generation (kWh)" />
|
||||||
<Bar dataKey="consumption" fill={consumptionColor} name="Consumption (kWh)">
|
|
||||||
{/* You can still apply individual cell colors if needed, but a single fill is often clearer for categories */}
|
|
||||||
</Bar>
|
|
||||||
|
|
||||||
{/* Bar for Generation */}
|
|
||||||
<Bar dataKey="generation" fill={generationColor} name="Generation (kWh)">
|
|
||||||
{/* You can still apply individual cell colors if needed */}
|
|
||||||
</Bar>
|
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,6 +34,11 @@ const calculateMonthlyTotal = (dataArray: number[]): number => {
|
|||||||
return dataArray.reduce((sum, value) => sum + value, 0);
|
return dataArray.reduce((sum, value) => sum + value, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateYearlyDataInRange = (min: number, max: number) =>
|
||||||
|
Array(365)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => Math.floor(Math.random() * (max - min + 1)) + min);
|
||||||
|
|
||||||
|
|
||||||
export const mockSiteData: Record<SiteName, SiteDetails> = {
|
export const mockSiteData: Record<SiteName, SiteDetails> = {
|
||||||
'Site A': {
|
'Site A': {
|
||||||
@ -41,8 +46,8 @@ export const mockSiteData: Record<SiteName, SiteDetails> = {
|
|||||||
inverterProvider: 'SolarEdge',
|
inverterProvider: 'SolarEdge',
|
||||||
emergencyContact: '+60 12-345 6789',
|
emergencyContact: '+60 12-345 6789',
|
||||||
lastSyncTimestamp: '2025-06-03 15:30:00',
|
lastSyncTimestamp: '2025-06-03 15:30:00',
|
||||||
consumptionData: [100, 110, 120, 130, 125, 140, 135, 120, 110, 180, 100, 130, 90, 150, 160, 170, 180, 175, 160], // Example daily kWh for 19 days
|
consumptionData: generateYearlyDataInRange(80, 250),
|
||||||
generationData: [80, 90, 95, 105, 110, 120, 115, 150, 130, 160, 120, 140, 120, 140, 130, 140, 150, 155, 140], // Example daily kWh for 19 days
|
generationData: generateYearlyDataInRange(80, 250),
|
||||||
systemStatus: 'Normal',
|
systemStatus: 'Normal',
|
||||||
temperature: '35°C',
|
temperature: '35°C',
|
||||||
solarPower: 108.4,
|
solarPower: 108.4,
|
||||||
@ -50,15 +55,15 @@ export const mockSiteData: Record<SiteName, SiteDetails> = {
|
|||||||
installedPower: 174.9, // kWp
|
installedPower: 174.9, // kWp
|
||||||
gridImportPrice_RM_per_kWh: 0.50, // Example: RM 0.50 per kWh
|
gridImportPrice_RM_per_kWh: 0.50, // Example: RM 0.50 per kWh
|
||||||
solarExportTariff_RM_per_kWh: 0.30, // Example: RM 0.30 per kWh
|
solarExportTariff_RM_per_kWh: 0.30, // Example: RM 0.30 per kWh
|
||||||
theoreticalMaxGeneration_kWh: 25000, // Example: Theoretical max for 174.9 kWp over a month in Malaysia
|
theoreticalMaxGeneration_kWh: 80000, // Example: Theoretical max for 174.9 kWp over a month in Malaysia
|
||||||
},
|
},
|
||||||
'Site B': {
|
'Site B': {
|
||||||
location: 'Kuala Lumpur, Wilayah Persekutuan',
|
location: 'Kuala Lumpur, Wilayah Persekutuan',
|
||||||
inverterProvider: 'Huawei',
|
inverterProvider: 'Huawei',
|
||||||
emergencyContact: '+60 19-876 5432',
|
emergencyContact: '+60 19-876 5432',
|
||||||
lastSyncTimestamp: '2025-06-02 10:15:00',
|
lastSyncTimestamp: '2025-06-02 10:15:00',
|
||||||
consumptionData: [90, 100, 110, 120, 115, 130, 125, 110, 100, 170, 90, 120, 80, 140, 150, 160, 170, 165, 150],
|
consumptionData: generateYearlyDataInRange(200, 450),
|
||||||
generationData: [70, 80, 85, 95, 100, 110, 105, 140, 120, 150, 110, 130, 110, 130, 120, 130, 140, 145, 130],
|
generationData: generateYearlyDataInRange(200, 450),
|
||||||
systemStatus: 'Normal',
|
systemStatus: 'Normal',
|
||||||
temperature: '32°C',
|
temperature: '32°C',
|
||||||
solarPower: 95.2,
|
solarPower: 95.2,
|
||||||
@ -66,15 +71,15 @@ export const mockSiteData: Record<SiteName, SiteDetails> = {
|
|||||||
installedPower: 150.0,
|
installedPower: 150.0,
|
||||||
gridImportPrice_RM_per_kWh: 0.52,
|
gridImportPrice_RM_per_kWh: 0.52,
|
||||||
solarExportTariff_RM_per_kWh: 0.32,
|
solarExportTariff_RM_per_kWh: 0.32,
|
||||||
theoreticalMaxGeneration_kWh: 20000,
|
theoreticalMaxGeneration_kWh: 190000,
|
||||||
},
|
},
|
||||||
'Site C': {
|
'Site C': {
|
||||||
location: 'Johor Bahru, Johor',
|
location: 'Johor Bahru, Johor',
|
||||||
inverterProvider: 'Enphase',
|
inverterProvider: 'Enphase',
|
||||||
emergencyContact: '+60 13-555 1234',
|
emergencyContact: '+60 13-555 1234',
|
||||||
lastSyncTimestamp: '2025-06-03 08:00:00',
|
lastSyncTimestamp: '2025-06-03 08:00:00',
|
||||||
consumptionData: [110, 120, 130, 140, 135, 150, 145, 130, 120, 190, 110, 140, 100, 160, 170, 180, 190, 185, 170],
|
consumptionData: generateYearlyDataInRange(400, 550),
|
||||||
generationData: [50, 60, 65, 75, 80, 90, 85, 100, 90, 110, 80, 90, 80, 90, 80, 90, 100, 105, 90],
|
generationData: generateYearlyDataInRange(400, 550),
|
||||||
systemStatus: 'Faulty',
|
systemStatus: 'Faulty',
|
||||||
temperature: '30°C',
|
temperature: '30°C',
|
||||||
solarPower: 25.0,
|
solarPower: 25.0,
|
||||||
@ -82,6 +87,6 @@ export const mockSiteData: Record<SiteName, SiteDetails> = {
|
|||||||
installedPower: 120.0,
|
installedPower: 120.0,
|
||||||
gridImportPrice_RM_per_kWh: 0.48,
|
gridImportPrice_RM_per_kWh: 0.48,
|
||||||
solarExportTariff_RM_per_kWh: 0.28,
|
solarExportTariff_RM_per_kWh: 0.28,
|
||||||
theoreticalMaxGeneration_kWh: 18000, // Lower theoretical max due to fault or smaller system
|
theoreticalMaxGeneration_kWh: 180000, // Lower theoretical max due to fault or smaller system
|
||||||
},
|
},
|
||||||
};
|
};
|
Loading…
x
Reference in New Issue
Block a user