feature/syasya/testlayout #7
							
								
								
									
										2
									
								
								App.tsx
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								App.tsx
									
									
									
									
									
								
							@ -32,7 +32,7 @@ function App({ children }: PropsWithChildren) {
 | 
			
		||||
        <div
 | 
			
		||||
            className={`${(themeConfig.sidebar && 'toggle-sidebar') || ''} ${themeConfig.menu} ${themeConfig.layout} ${
 | 
			
		||||
                themeConfig.rtlClass
 | 
			
		||||
            } main-section relative font-nunito text-sm font-normal antialiased`}
 | 
			
		||||
            } main-section relative font-exo2 text-sm font-normal antialiased`}
 | 
			
		||||
        >
 | 
			
		||||
            {isLoading ? <Loading /> : children}
 | 
			
		||||
            <Toaster />
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								app/(admin)/adminDashboard/dashlayout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/(admin)/adminDashboard/dashlayout.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
// components/layouts/DashboardLayout.tsx
 | 
			
		||||
'use client';
 | 
			
		||||
 | 
			
		||||
import { useSelector } from 'react-redux';
 | 
			
		||||
import { IRootState } from '@/store';
 | 
			
		||||
import Sidebar from '@/components/layouts/sidebar';
 | 
			
		||||
import Header from '@/components/layouts/header'; // Correctly import the consolidated Header
 | 
			
		||||
 | 
			
		||||
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
 | 
			
		||||
    const themeConfig = useSelector((state: IRootState) => state.themeConfig);
 | 
			
		||||
    const semidark = useSelector((state: IRootState) => state.themeConfig.semidark);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`${semidark ? 'dark' : ''} ${themeConfig.sidebar ? 'toggle-sidebar' : ''}`}>
 | 
			
		||||
            {/* Only render the single, consolidated Header component */}
 | 
			
		||||
           <Header/>
 | 
			
		||||
            <Sidebar />
 | 
			
		||||
 | 
			
		||||
            <div className="main-content">
 | 
			
		||||
                {/* This is where your page content will be injected */}
 | 
			
		||||
                <div className="p-6 space-y-6 min-h-screen">
 | 
			
		||||
                    {children}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DashboardLayout;
 | 
			
		||||
							
								
								
									
										119
									
								
								app/(admin)/adminDashboard/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								app/(admin)/adminDashboard/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
			
		||||
// app/adminDashboard/page.tsx
 | 
			
		||||
'use client';
 | 
			
		||||
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { useSearchParams } from 'next/navigation';
 | 
			
		||||
 | 
			
		||||
import SiteSelector from '@/components/dashboards/SiteSelector';
 | 
			
		||||
import SiteStatus from '@/components/dashboards/SiteStatus';
 | 
			
		||||
import SystemOverview from '@/components/dashboards/SystemOverview'; // Import the new component
 | 
			
		||||
import EnergyLineChart from '@/components/dashboards/EnergyLineChart';
 | 
			
		||||
import KPI_Table from '@/components/dashboards/KPIStatus';
 | 
			
		||||
import MonthlyBarChart from '@/components/dashboards/MonthlyBarChart';
 | 
			
		||||
import DashboardLayout from './dashlayout';
 | 
			
		||||
 | 
			
		||||
import { SiteName, SiteDetails, mockSiteData } from '@/types/SiteData';
 | 
			
		||||
 | 
			
		||||
const AdminDashboard = () => {
 | 
			
		||||
    const searchParams = useSearchParams();
 | 
			
		||||
    const siteParam = searchParams?.get('site');
 | 
			
		||||
 | 
			
		||||
    const [selectedSite, setSelectedSite] = useState<SiteName>(() => {
 | 
			
		||||
        const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
 | 
			
		||||
        if (siteParam && validSiteNames.includes(siteParam as SiteName)) {
 | 
			
		||||
            return siteParam as SiteName;
 | 
			
		||||
        }
 | 
			
		||||
        return 'Site A';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
 | 
			
		||||
        if (siteParam && validSiteNames.includes(siteParam as SiteName) && siteParam !== selectedSite) {
 | 
			
		||||
            setSelectedSite(siteParam as SiteName);
 | 
			
		||||
        }
 | 
			
		||||
    }, [siteParam, selectedSite]);
 | 
			
		||||
 | 
			
		||||
    const currentSiteDetails: SiteDetails = mockSiteData[selectedSite] || {
 | 
			
		||||
        location: 'N/A',
 | 
			
		||||
        inverterProvider: 'N/A',
 | 
			
		||||
        emergencyContact: 'N/A',
 | 
			
		||||
        lastSyncTimestamp: 'N/A',
 | 
			
		||||
        consumptionData: [],
 | 
			
		||||
        generationData: [],
 | 
			
		||||
        systemStatus: 'N/A', // Fallback
 | 
			
		||||
        temperature: 'N/A', // Fallback
 | 
			
		||||
        solarPower: 0, // Fallback
 | 
			
		||||
        realTimePower: 0, // Fallback
 | 
			
		||||
        installedPower: 0, // Fallback
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleCSVExport = () => {
 | 
			
		||||
        alert('Exported raw data to CSV (mock)');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handlePDFExport = () => {
 | 
			
		||||
        alert('Exported chart images to PDF (mock)');
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
         <DashboardLayout>
 | 
			
		||||
            <div className="px-6 space-y-6">
 | 
			
		||||
                <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">
 | 
			
		||||
                    {/* First Column: Site Selector and Site Status */}
 | 
			
		||||
                    <div className="space-y-4">
 | 
			
		||||
                        <SiteSelector selectedSite={selectedSite} setSelectedSite={setSelectedSite} />
 | 
			
		||||
                        <SiteStatus
 | 
			
		||||
                            selectedSite={selectedSite}
 | 
			
		||||
                            location={currentSiteDetails.location}
 | 
			
		||||
                            inverterProvider={currentSiteDetails.inverterProvider}
 | 
			
		||||
                            emergencyContact={currentSiteDetails.emergencyContact}
 | 
			
		||||
                            lastSyncTimestamp={currentSiteDetails.lastSyncTimestamp}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {/* Second Column: KPI Table */}
 | 
			
		||||
                    <div> {/* This div will now be the second column */}
 | 
			
		||||
                        <KPI_Table />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* System Overview - Now covers the whole width */}
 | 
			
		||||
                <SystemOverview
 | 
			
		||||
                    status={currentSiteDetails.systemStatus}
 | 
			
		||||
                    temperature={currentSiteDetails.temperature}
 | 
			
		||||
                    solarPower={currentSiteDetails.solarPower}
 | 
			
		||||
                    realTimePower={currentSiteDetails.realTimePower}
 | 
			
		||||
                    installedPower={currentSiteDetails.installedPower}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                {/* Charts Section */}
 | 
			
		||||
                <div className="grid md:grid-cols-2 gap-6">
 | 
			
		||||
                    <div className="pb-16">
 | 
			
		||||
                        <EnergyLineChart
 | 
			
		||||
                            consumptionData={currentSiteDetails.consumptionData}
 | 
			
		||||
                            generationData={currentSiteDetails.generationData}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="pb-16">
 | 
			
		||||
                        <MonthlyBarChart />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div className="flex gap-4">
 | 
			
		||||
                    <button onClick={handleCSVExport} className="btn-primary">
 | 
			
		||||
                        Export Raw Data to CSV
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button onClick={handlePDFExport} className="btn-primary">
 | 
			
		||||
                        Export Chart Images to PDF
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </DashboardLayout>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AdminDashboard;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										46
									
								
								app/(admin)/sites/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/(admin)/sites/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
'use client';
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import DashboardLayout from '../adminDashboard/dashlayout';
 | 
			
		||||
import SiteCard from '@/components/dashboards/SiteCard'; // Import the new SiteCard component
 | 
			
		||||
import { mockSiteData, SiteName } from '@/types/SiteData'; // Import your mock data and SiteName type
 | 
			
		||||
 | 
			
		||||
const SitesPage = () => {
 | 
			
		||||
    // Helper function to determine status (can be externalized if used elsewhere)
 | 
			
		||||
    const getSiteStatus = (siteName: SiteName): string => {
 | 
			
		||||
        const statusMap: Record<SiteName, string> = {
 | 
			
		||||
            'Site A': 'Active',
 | 
			
		||||
            'Site B': 'Inactive',
 | 
			
		||||
            'Site C': 'Faulty',
 | 
			
		||||
        };
 | 
			
		||||
        return statusMap[siteName];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <DashboardLayout>
 | 
			
		||||
            <div className="p-6 space-y-6">
 | 
			
		||||
                <h1 className="text-2xl font-bold mb-6 dark:text-white-light">All Sites Overview</h1>
 | 
			
		||||
 | 
			
		||||
                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
 | 
			
		||||
                    {/* Iterate over the keys of mockSiteData (which are your SiteNames) */}
 | 
			
		||||
                    {Object.keys(mockSiteData).map((siteNameKey) => {
 | 
			
		||||
                        const siteName = siteNameKey as SiteName; // Cast to SiteName type
 | 
			
		||||
                        const siteDetails = mockSiteData[siteName];
 | 
			
		||||
                        const siteStatus = getSiteStatus(siteName);
 | 
			
		||||
 | 
			
		||||
                        return (
 | 
			
		||||
                            <SiteCard
 | 
			
		||||
                                key={siteName} // Important for React list rendering
 | 
			
		||||
                                siteName={siteName}
 | 
			
		||||
                                details={siteDetails}
 | 
			
		||||
                                status={siteStatus}
 | 
			
		||||
                            />
 | 
			
		||||
                        );
 | 
			
		||||
                    })}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </DashboardLayout>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SitesPage;
 | 
			
		||||
@ -25,6 +25,9 @@ const InverterViewPage = (props: Props) => {
 | 
			
		||||
 | 
			
		||||
    const fetchData = async () => {
 | 
			
		||||
        try {
 | 
			
		||||
            if (!params || !params.id) {
 | 
			
		||||
                throw new Error("Invalid params or params.id is missing");
 | 
			
		||||
            }
 | 
			
		||||
            const res = await axios.get(`https://api-a.fomware.com.cn/asset/v1/list?type=2&key=${params.id.toString()}`, {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    "Authorization": "Bearer " + process.env.NEXT_PUBLIC_CHINT_TOKEN
 | 
			
		||||
@ -45,7 +48,7 @@ const InverterViewPage = (props: Props) => {
 | 
			
		||||
 | 
			
		||||
        {loading ? <p>Loading...</p> : (
 | 
			
		||||
        <>
 | 
			
		||||
        <PanelCodeHighlight title={params.id.toString() || ""}>
 | 
			
		||||
        <PanelCodeHighlight title={params?.id?.toString() || ""}>
 | 
			
		||||
            <div className="mb-5">
 | 
			
		||||
                {isMounted && (
 | 
			
		||||
                    <Tab.Group>
 | 
			
		||||
 | 
			
		||||
@ -6,43 +6,33 @@ import Header from '@/components/layouts/header';
 | 
			
		||||
import MainContainer from '@/components/layouts/main-container';
 | 
			
		||||
import Overlay from '@/components/layouts/overlay';
 | 
			
		||||
import ScrollToTop from '@/components/layouts/scroll-to-top';
 | 
			
		||||
import Setting from '@/components/layouts/setting';
 | 
			
		||||
import Sidebar from '@/components/layouts/sidebar';
 | 
			
		||||
import Portals from '@/components/portals';
 | 
			
		||||
import withAuth from '@/hoc/withAuth';
 | 
			
		||||
import { FC } from 'react';
 | 
			
		||||
import withAuth from '@/hoc/withAuth'; // make sure this matches your export style
 | 
			
		||||
import { FC, ReactNode } from 'react';
 | 
			
		||||
 | 
			
		||||
const DefaultLayout: FC<{ children: React.ReactNode }> = ({ children }) => {
 | 
			
		||||
interface DefaultLayoutProps {
 | 
			
		||||
  children: ReactNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DefaultLayout: FC<DefaultLayoutProps> = ({ children }) => {
 | 
			
		||||
  return (
 | 
			
		||||
        <>
 | 
			
		||||
            {/* BEGIN MAIN CONTAINER */}
 | 
			
		||||
    <div className="relative">
 | 
			
		||||
      <Overlay />
 | 
			
		||||
      <ScrollToTop />
 | 
			
		||||
 | 
			
		||||
      <MainContainer>
 | 
			
		||||
                    {/* BEGIN SIDEBAR */}
 | 
			
		||||
        <Sidebar />
 | 
			
		||||
                    {/* END SIDEBAR */}
 | 
			
		||||
        <div className="main-content flex min-h-screen flex-col">
 | 
			
		||||
                        {/* BEGIN TOP NAVBAR */}
 | 
			
		||||
          <Header />
 | 
			
		||||
                        {/* END TOP NAVBAR */}
 | 
			
		||||
 | 
			
		||||
                        {/* BEGIN CONTENT AREA */}
 | 
			
		||||
          <ContentAnimation>{children}</ContentAnimation>
 | 
			
		||||
                        {/* END CONTENT AREA */}
 | 
			
		||||
 | 
			
		||||
                        {/* BEGIN FOOTER */}
 | 
			
		||||
          <Footer />
 | 
			
		||||
                        {/* END FOOTER */}
 | 
			
		||||
          <Portals />
 | 
			
		||||
        </div>
 | 
			
		||||
      </MainContainer>
 | 
			
		||||
    </div>
 | 
			
		||||
        </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withAuth(DefaultLayout);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,47 @@
 | 
			
		||||
'use client'
 | 
			
		||||
import { Metadata } from 'next';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
 | 
			
		||||
export const metadata: Metadata = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Sales = () => {
 | 
			
		||||
    return <div>starter page</div>;
 | 
			
		||||
  const [selectedSite, setSelectedSite] = useState('');
 | 
			
		||||
  const sites = ['Site A', 'Site B', 'Site C']; // replace with your actual site list
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="min-h-screen flex flex-col items-center justify-center p-4 bg-gray-50">
 | 
			
		||||
      <h1 className="text-3xl font-bold mb-4 text-gray-800">
 | 
			
		||||
        Welcome to Rooftop Dashboard !
 | 
			
		||||
      </h1>
 | 
			
		||||
      <h2 className="text-2xl font-bold mb-4 text-gray-800">
 | 
			
		||||
        Select a site to get started.
 | 
			
		||||
      </h2>
 | 
			
		||||
      <div className="w-full max-w-sm">
 | 
			
		||||
        <label className="block text-gray-700 mb-2">Select Site:</label>
 | 
			
		||||
        <select
 | 
			
		||||
          value={selectedSite}
 | 
			
		||||
          onChange={(e) => setSelectedSite(e.target.value)}
 | 
			
		||||
          className="w-full p-2 border-2 border-yellow-300 rounded-md"
 | 
			
		||||
        >
 | 
			
		||||
          <option value="" disabled>
 | 
			
		||||
            -- Choose a site --
 | 
			
		||||
          </option>
 | 
			
		||||
          {sites.map((site) => (
 | 
			
		||||
            <option key={site} value={site}>
 | 
			
		||||
              {site}
 | 
			
		||||
            </option>
 | 
			
		||||
          ))}
 | 
			
		||||
        </select>
 | 
			
		||||
        {selectedSite && (
 | 
			
		||||
            <div className="flex flex-col space-y-2">
 | 
			
		||||
                <p className="mt-4 text-green-700">You selected: {selectedSite}</p>
 | 
			
		||||
                <button className="btn-primary">Go to Dashboard</button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Sales;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,17 @@
 | 
			
		||||
'use client';
 | 
			
		||||
import ProviderComponent from '@/components/layouts/provider-component';
 | 
			
		||||
import 'react-perfect-scrollbar/dist/css/styles.css';
 | 
			
		||||
import '../styles/tailwind.css';
 | 
			
		||||
import { Metadata } from 'next';
 | 
			
		||||
import { Nunito } from 'next/font/google';
 | 
			
		||||
import { Exo_2 } from "next/font/google";
 | 
			
		||||
 | 
			
		||||
const exo2 = Exo_2({
 | 
			
		||||
  subsets: ["latin"],
 | 
			
		||||
  variable: "--font-exo2",
 | 
			
		||||
  weight: ["200", "400"],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const metadata: Metadata = {
 | 
			
		||||
    title: {
 | 
			
		||||
        template: '%s | Rooftop Energy - Admin',
 | 
			
		||||
        default: 'Rooftop Energy - Admin',
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
const nunito = Nunito({
 | 
			
		||||
    weight: ['400', '500', '600', '700', '800'],
 | 
			
		||||
    subsets: ['latin'],
 | 
			
		||||
@ -20,7 +22,7 @@ const nunito = Nunito({
 | 
			
		||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <html lang="en">
 | 
			
		||||
            <body className={nunito.variable}>
 | 
			
		||||
            <body className={exo2.variable}>
 | 
			
		||||
                <ProviderComponent>{children}</ProviderComponent>
 | 
			
		||||
            </body>
 | 
			
		||||
        </html>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										130
									
								
								components/dashboards/EnergyLineChart.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								components/dashboards/EnergyLineChart.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,130 @@
 | 
			
		||||
// components/dashboards/EnergyLineChart.tsx
 | 
			
		||||
'use client';
 | 
			
		||||
import { useRef } 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';
 | 
			
		||||
 | 
			
		||||
ChartJS.register(
 | 
			
		||||
  LineElement,
 | 
			
		||||
  PointElement,
 | 
			
		||||
  LinearScale,
 | 
			
		||||
  CategoryScale,
 | 
			
		||||
  Title,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  Legend,
 | 
			
		||||
  zoomPlugin
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Define props interface for EnergyLineChart
 | 
			
		||||
interface EnergyLineChartProps {
 | 
			
		||||
    consumptionData: number[];
 | 
			
		||||
    generationData: number[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const labels = [
 | 
			
		||||
  '08:00', '08:30', '09:00', '09:30', '10:00',
 | 
			
		||||
  '10:30', '11:00', '11:30', '12:00', '12:30',
 | 
			
		||||
  '13:00', '13:30', '14:00', '14:30', '15:00',
 | 
			
		||||
  '15:30', '16:00', '16:30', '17:00',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const EnergyLineChart = ({ consumptionData, generationData }: EnergyLineChartProps) => {
 | 
			
		||||
  const chartRef = useRef<any>(null);
 | 
			
		||||
 | 
			
		||||
  // Calculate suggestedMax dynamically based on the current data
 | 
			
		||||
  const allDataPoints = [...consumptionData, ...generationData];
 | 
			
		||||
  const maxDataValue = allDataPoints.length > 0 ? Math.max(...allDataPoints) : 0; // Handle empty array
 | 
			
		||||
  const yAxisSuggestedMax = maxDataValue * 1.15; // Adds 15% padding
 | 
			
		||||
 | 
			
		||||
  const data = {
 | 
			
		||||
    labels,
 | 
			
		||||
    datasets: [
 | 
			
		||||
      {
 | 
			
		||||
        label: 'Consumption',
 | 
			
		||||
        data: consumptionData, // Use prop data
 | 
			
		||||
        borderColor: '#8884d8',
 | 
			
		||||
        tension: 0.4,
 | 
			
		||||
        fill: false,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        label: 'Generation',
 | 
			
		||||
        data: generationData, // Use prop data
 | 
			
		||||
        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: {
 | 
			
		||||
      y: {
 | 
			
		||||
        beginAtZero: true,
 | 
			
		||||
        suggestedMax: yAxisSuggestedMax, // Use the dynamically calculated value
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  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-96 w-full">
 | 
			
		||||
        <div className="flex justify-between items-center mb-2">
 | 
			
		||||
          <h2 className="text-lg font-bold dark:text-white-light">Power/Energy Consumption & Generation</h2>
 | 
			
		||||
          <button
 | 
			
		||||
            onClick={handleResetZoom}
 | 
			
		||||
            className="btn-primary text-sm"
 | 
			
		||||
          >
 | 
			
		||||
            Reset Zoom
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="h-full w-full">
 | 
			
		||||
          <Line ref={chartRef} data={data} options={options} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default EnergyLineChart;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								components/dashboards/KPIStatus.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								components/dashboards/KPIStatus.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
const KPI_Table = () => {
 | 
			
		||||
  const data = [
 | 
			
		||||
    { kpi: 'Monthly Yield', value: '22000 kWh' },
 | 
			
		||||
    { kpi: 'Monthly Consumption', value: '24000 kWh' },
 | 
			
		||||
    { kpi: 'Monthly Grid Draw', value: '2000 kWh' },
 | 
			
		||||
    { kpi: 'Efficiency', value: '87%' },
 | 
			
		||||
    { kpi: 'Monthly Saved', value: 'RM 200' },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h2 className="text-lg font-bold mb-2">Monthly KPI</h2>
 | 
			
		||||
      <table className="w-full border-collapse border">
 | 
			
		||||
        <thead>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th className="border p-2 text-left">KPI</th>
 | 
			
		||||
            <th className="border p-2 text-left">Value</th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
          {data.map((row) => (
 | 
			
		||||
            <tr key={row.kpi}>
 | 
			
		||||
              <td className="border p-2">{row.kpi}</td>
 | 
			
		||||
              <td className="border p-2">{row.value}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          ))}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default KPI_Table;
 | 
			
		||||
							
								
								
									
										48
									
								
								components/dashboards/MonthlyBarChart.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								components/dashboards/MonthlyBarChart.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import {
 | 
			
		||||
  BarChart,
 | 
			
		||||
  Bar,
 | 
			
		||||
  XAxis,
 | 
			
		||||
  YAxis,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  ResponsiveContainer,
 | 
			
		||||
  Cell
 | 
			
		||||
} from 'recharts';
 | 
			
		||||
 | 
			
		||||
const monthlyData = [
 | 
			
		||||
  { month: 'January', usage: 3000 },
 | 
			
		||||
  { month: 'February', usage: 3200 },
 | 
			
		||||
  { month: 'March', usage: 2800 },
 | 
			
		||||
  { month: 'April', usage: 3100 },
 | 
			
		||||
  { month: 'May', usage: 3300 },
 | 
			
		||||
  { month: 'June', usage: 3500 },
 | 
			
		||||
  { month: 'July', usage: 3400 },
 | 
			
		||||
  { month: 'August', usage: 3600 },
 | 
			
		||||
  { month: 'September', usage: 3200 },
 | 
			
		||||
  { month: 'October', usage: 3000 },
 | 
			
		||||
  { month: 'November', usage: 2900 },
 | 
			
		||||
  { month: 'December', usage: 3100 },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const barColors = ['#003049', '#669bbc']; // Alternate between yellow tones
 | 
			
		||||
 | 
			
		||||
const MonthlyBarChart = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="h-80">
 | 
			
		||||
      <h2 className="text-lg font-bold mb-2">Monthly Energy Consumption</h2>
 | 
			
		||||
      <ResponsiveContainer width="100%" height="100%">
 | 
			
		||||
        <BarChart data={monthlyData}>
 | 
			
		||||
          <XAxis dataKey="month" />
 | 
			
		||||
          <YAxis />
 | 
			
		||||
          <Tooltip />
 | 
			
		||||
          <Bar dataKey="usage">
 | 
			
		||||
            {monthlyData.map((entry, index) => (
 | 
			
		||||
              <Cell key={`cell-${index}`} fill={barColors[index % barColors.length]} />
 | 
			
		||||
            ))}
 | 
			
		||||
          </Bar>
 | 
			
		||||
        </BarChart>
 | 
			
		||||
      </ResponsiveContainer>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default MonthlyBarChart;
 | 
			
		||||
							
								
								
									
										63
									
								
								components/dashboards/SiteCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								components/dashboards/SiteCard.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
// components/dashboards/SiteCard.tsx
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import Link from 'next/link'; // Import Link from Next.js
 | 
			
		||||
import { SiteName, SiteDetails } from '@/types/SiteData'; // Adjust path as necessary
 | 
			
		||||
 | 
			
		||||
interface SiteCardProps {
 | 
			
		||||
    siteName: SiteName;
 | 
			
		||||
    details: SiteDetails;
 | 
			
		||||
    status: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SiteCard: React.FC<SiteCardProps> = ({ siteName, details, status }) => {
 | 
			
		||||
    const statusColorClass =
 | 
			
		||||
        status === 'Active' ? 'text-green-500' :
 | 
			
		||||
        status === 'Inactive' ? 'text-orange-500' :
 | 
			
		||||
        'text-red-500';
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="bg-white p-4 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light flex flex-col space-y-2">
 | 
			
		||||
            <h3 className="text-xl font-bold text-primary-600 dark:text-primary-400 border-b pb-2 mb-2">
 | 
			
		||||
                {siteName}
 | 
			
		||||
            </h3>
 | 
			
		||||
 | 
			
		||||
            <div className="flex justify-between items-center">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Status:</p>
 | 
			
		||||
                <p className={`font-semibold ${statusColorClass}`}>{status}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex justify-between items-center">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Location:</p>
 | 
			
		||||
                <p className="font-semibold">{details.location}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex justify-between items-center">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Inverter Provider:</p>
 | 
			
		||||
                <p className="font-semibold">{details.inverterProvider}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex justify-between items-center">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Emergency Contact:</p>
 | 
			
		||||
                <p className="font-semibold">{details.emergencyContact}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex justify-between items-center">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Last Sync:</p>
 | 
			
		||||
                <p className="font-semibold">{details.lastSyncTimestamp}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {/* New: View Dashboard Button */}
 | 
			
		||||
            <Link
 | 
			
		||||
                href={{
 | 
			
		||||
                    pathname: '/adminDashboard', // Path to your AdminDashboard page
 | 
			
		||||
                    query: { site: siteName }, // Pass the siteName as a query parameter
 | 
			
		||||
                }}
 | 
			
		||||
                className="mt-4 w-full text-center text-sm btn-primary" // Tailwind classes for basic button styling
 | 
			
		||||
            >
 | 
			
		||||
                View Dashboard
 | 
			
		||||
            </Link>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SiteCard;
 | 
			
		||||
							
								
								
									
										26
									
								
								components/dashboards/SiteSelector.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								components/dashboards/SiteSelector.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
 | 
			
		||||
import type { SiteName } from '@/components/dashboards/SiteStatus';
 | 
			
		||||
 | 
			
		||||
type SiteSelectorProps = {
 | 
			
		||||
  selectedSite: SiteName;
 | 
			
		||||
  setSelectedSite: (site: SiteName) => void;
 | 
			
		||||
};
 | 
			
		||||
const SiteSelector = ({ selectedSite, setSelectedSite }: SiteSelectorProps) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col ">
 | 
			
		||||
      <label htmlFor="site" className="font-semibold text-lg">Select Site:</label>
 | 
			
		||||
      <select
 | 
			
		||||
        id="site"
 | 
			
		||||
        className="border p-2 rounded"
 | 
			
		||||
        value={selectedSite}
 | 
			
		||||
        onChange={(e) => setSelectedSite(e.target.value as SiteName)}
 | 
			
		||||
      >
 | 
			
		||||
        <option>Site A</option>
 | 
			
		||||
        <option>Site B</option>
 | 
			
		||||
        <option>Site C</option>
 | 
			
		||||
      </select>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SiteSelector;
 | 
			
		||||
							
								
								
									
										67
									
								
								components/dashboards/SiteStatus.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								components/dashboards/SiteStatus.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
			
		||||
export type SiteName = 'Site A' | 'Site B' | 'Site C';
 | 
			
		||||
 | 
			
		||||
interface SiteStatusProps {
 | 
			
		||||
    selectedSite: SiteName;
 | 
			
		||||
    location: string;
 | 
			
		||||
    inverterProvider: string;
 | 
			
		||||
    emergencyContact: string;
 | 
			
		||||
    lastSyncTimestamp: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SiteStatus = ({
 | 
			
		||||
    selectedSite,
 | 
			
		||||
    location,
 | 
			
		||||
    inverterProvider,
 | 
			
		||||
    emergencyContact,
 | 
			
		||||
    lastSyncTimestamp,
 | 
			
		||||
}: SiteStatusProps) => {
 | 
			
		||||
    const statusMap: Record<SiteName, string> = {
 | 
			
		||||
        'Site A': 'Active',
 | 
			
		||||
        'Site B': 'Inactive',
 | 
			
		||||
        'Site C': 'Faulty',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="bg-white p-4 rounded-lg shadow-md space-y-2 dark:bg-gray-800 dark:text-white-light">
 | 
			
		||||
            <h2 className="text-xl font-semibold mb-3">Site Details</h2>
 | 
			
		||||
 | 
			
		||||
            {/* Status */}
 | 
			
		||||
            <div className="flex justify-between items-center text-base">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Status:</p>
 | 
			
		||||
                <p className={`font-semibold ${
 | 
			
		||||
                    statusMap[selectedSite] === 'Active' ? 'text-green-500' :
 | 
			
		||||
                    statusMap[selectedSite] === 'Inactive' ? 'text-orange-500' :
 | 
			
		||||
                    'text-red-500'
 | 
			
		||||
                }`}>
 | 
			
		||||
                    {statusMap[selectedSite]}
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {/* Location */}
 | 
			
		||||
            <div className="flex justify-between items-center text-base">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Location:</p>
 | 
			
		||||
                <p className="font-medium">{location}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {/* Inverter Provider */}
 | 
			
		||||
            <div className="flex justify-between items-center text-base">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Inverter Provider:</p>
 | 
			
		||||
                <p className="font-medium">{inverterProvider}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {/* Emergency Contact */}
 | 
			
		||||
            <div className="flex justify-between items-center text-base">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Emergency Contact:</p>
 | 
			
		||||
                <p className="font-medium">{emergencyContact}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {/* Last Sync */}
 | 
			
		||||
            <div className="flex justify-between items-center text-base">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Last Sync:</p>
 | 
			
		||||
                <p className="font-medium">{lastSyncTimestamp}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SiteStatus;
 | 
			
		||||
							
								
								
									
										137
									
								
								components/dashboards/SystemOverview.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								components/dashboards/SystemOverview.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
			
		||||
// components/dashboards/SystemOverview.tsx
 | 
			
		||||
'use client'; // This component will contain client-side interactivity and use browser APIs
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import Image from 'next/image'; // Import Next.js Image component for optimized images
 | 
			
		||||
 | 
			
		||||
// You'll need actual SVG/PNG images for these.
 | 
			
		||||
// For demonstration, I'll use placeholders. You'll replace these paths.
 | 
			
		||||
import solarPanelIcon from '@/public/images/solarpanel.png'; // Example path
 | 
			
		||||
import houseIcon from '@/public/images/building.png'; // Example path
 | 
			
		||||
import gridTowerIcon from '@/public/images/gridtower.png'; // Example path
 | 
			
		||||
interface SystemOverviewProps {
 | 
			
		||||
    status: string; // e.g., "Normal", "Faulty"
 | 
			
		||||
    temperature: string; // e.g., "35°C"
 | 
			
		||||
    solarPower: number; // Power generated by solar (kW)
 | 
			
		||||
    realTimePower: number; // Real-time power used (kW)
 | 
			
		||||
    installedPower: number; // Installed capacity (kWp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SystemOverview: React.FC<SystemOverviewProps> = ({
 | 
			
		||||
    status,
 | 
			
		||||
    temperature,
 | 
			
		||||
    solarPower,
 | 
			
		||||
    realTimePower,
 | 
			
		||||
    installedPower,
 | 
			
		||||
}) => {
 | 
			
		||||
    const statusColor = status === 'Normal' ? 'text-green-500' : 'text-red-500';
 | 
			
		||||
 | 
			
		||||
    // Increased icon size for better visibility, adjust as needed
 | 
			
		||||
    const iconBaseSize = 100; // Original was 80, let's use 100-120 range.
 | 
			
		||||
    const solarIconSize = iconBaseSize * 1.2; // Solar panel slightly larger
 | 
			
		||||
    const otherIconSize = iconBaseSize * 1.1; // House and Grid slightly larger
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="bg-white p-6 rounded-lg shadow-md dark:bg-gray-800 dark:text-white-light col-span-full md:col-span-1 flex flex-col">
 | 
			
		||||
            {/* Top Status & Temperature Info */}
 | 
			
		||||
            <div className="flex items-center space-x-2 mb-2">
 | 
			
		||||
                <p className="text-gray-600 dark:text-gray-400 font-medium">Status:</p>
 | 
			
		||||
                <span className={`font-bold ${statusColor}`}>
 | 
			
		||||
                    {/* Display checkmark only for 'Normal' status */}
 | 
			
		||||
                    {status === 'Normal'}
 | 
			
		||||
                    {status}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <p className="text-sm text-gray-600 dark:text-gray-400 mb-6">{temperature}</p>
 | 
			
		||||
 | 
			
		||||
            {/* Main Visual Section for Icons, SVG Lines, and Power Metrics */}
 | 
			
		||||
            {/* This container will control the overall aspect ratio and height of the visual elements */}
 | 
			
		||||
            <div className="relative flex-grow flex items-center justify-center min-h-[300px] sm:min-h-[350px] md:min-h-[400px] lg:min-h-[450px]">
 | 
			
		||||
 | 
			
		||||
                {/* SVG for drawing the connecting lines */}
 | 
			
		||||
                {/* viewBox="0 0 1000 500" gives a 2:1 aspect ratio for internal coordinates. */}
 | 
			
		||||
                <svg
 | 
			
		||||
                    className="absolute inset-0 w-full h-full"
 | 
			
		||||
                    viewBox="0 0 1000 500"
 | 
			
		||||
                    preserveAspectRatio="xMidYMid meet" // Scales proportionally within its container
 | 
			
		||||
                >
 | 
			
		||||
                    <defs>
 | 
			
		||||
                        {/* Define a reusable arrowhead marker */}
 | 
			
		||||
                        <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="0" refY="3.5" orient="auto">
 | 
			
		||||
                            <polygon points="0 0, 10 3.5, 0 7" fill="#60A5FA" /> {/* Fill with blue-400 color */}
 | 
			
		||||
                        </marker>
 | 
			
		||||
                    </defs>
 | 
			
		||||
 | 
			
		||||
                    {/* Path 1: Solar Panel (top-center) to House (top-right) */}
 | 
			
		||||
                    {/* This path roughly follows the curved line from your image */}
 | 
			
		||||
                    {/* M: moveto, C: cubic Bezier curve (x1 y1, x2 y2, x y) */}
 | 
			
		||||
                    <path
 | 
			
		||||
                        d="M 500 130 C 450 180, 200 250, 270 340"
 | 
			
		||||
                        fill="none"
 | 
			
		||||
                        stroke="#60A5FA" // Tailwind blue-400
 | 
			
		||||
                        strokeWidth="4"
 | 
			
		||||
                       // Creates a dashed line (8px dash, 4px gap)
 | 
			
		||||
                        markerEnd="url(#arrowhead)" // Attach the arrowhead
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    {/* Path 2: House (mid-right) to Grid Tower (mid-left) */}
 | 
			
		||||
                    {/* A relatively straight line between the house and grid tower */}
 | 
			
		||||
                    <path
 | 
			
		||||
                        d="M 290 395 L 700 395"
 | 
			
		||||
                        fill="none"
 | 
			
		||||
                        stroke="#60A5FA"
 | 
			
		||||
                        strokeWidth="4"
 | 
			
		||||
                        
 | 
			
		||||
                        markerEnd="url(#arrowhead)"
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    {/* Path 3: Grid Tower (top-left) to Solar Panel (top-left) */}
 | 
			
		||||
                    {/* This path roughly connects the grid to the solar panel */}
 | 
			
		||||
                    <path
 | 
			
		||||
                        d="M 730 340 C 780 250, 550 150, 500 130"
 | 
			
		||||
                        fill="none"
 | 
			
		||||
                        stroke="#60A5FA"
 | 
			
		||||
                        strokeWidth="4"
 | 
			
		||||
                       
 | 
			
		||||
                        markerEnd="url(#arrowhead)"
 | 
			
		||||
                    />
 | 
			
		||||
                </svg>
 | 
			
		||||
 | 
			
		||||
                {/* --- Icons (Images) --- */}
 | 
			
		||||
                {/* Positioned using absolute CSS with percentages and translate for centering */}
 | 
			
		||||
                {/* Solar Panel Icon */}
 | 
			
		||||
                <div className="absolute flex flex-col items-center z-10"
 | 
			
		||||
                     style={{ top: '5%', left: '50%', transform: 'translateX(-50%)' }}>
 | 
			
		||||
                    <Image src={solarPanelIcon} alt="Solar Panels" width={solarIconSize} height={solarIconSize} />
 | 
			
		||||
                    <p className="mt-2 text-lg font-semibold dark:text-white-light">{solarPower} kW</p>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* House Icon */}
 | 
			
		||||
                <div className="absolute flex flex-col items-center z-10"
 | 
			
		||||
                     style={{ bottom: '5%', left: '25%', transform: 'translateX(-50%)' }}>
 | 
			
		||||
                    <Image src={houseIcon} alt="House" width={otherIconSize} height={otherIconSize} />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* Grid Tower Icon */}
 | 
			
		||||
                <div className="absolute flex flex-col items-center z-10"
 | 
			
		||||
                     style={{ bottom: '5%', right: '20%', transform: 'translateX(50%)' }}>
 | 
			
		||||
                    <Image src={gridTowerIcon} alt="Grid Tower" width={otherIconSize} height={otherIconSize} />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {/* --- Power Metrics Texts --- */}
 | 
			
		||||
                {/* Positioned relative to the main visual container */}
 | 
			
		||||
                <div className="absolute text-right z-10"
 | 
			
		||||
                     style={{ top: '25%', right: '5%' }}>
 | 
			
		||||
                    <p className="text-gray-600 dark:text-gray-400">Real-time power</p>
 | 
			
		||||
                    <p className="text-2xl font-bold text-primary-500">{realTimePower} kW</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="absolute text-right z-10"
 | 
			
		||||
                     style={{ top: '55%', right: '5%' }}>
 | 
			
		||||
                    <p className="text-gray-600 dark:text-gray-400">Installed power</p>
 | 
			
		||||
                    <p className="text-2xl font-bold text-primary-500">{installedPower} kWp</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SystemOverview;
 | 
			
		||||
							
								
								
									
										26
									
								
								components/layouts/sidebar-toggle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								components/layouts/sidebar-toggle.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
'use client';
 | 
			
		||||
 | 
			
		||||
import { useDispatch, useSelector } from 'react-redux';
 | 
			
		||||
import { toggleSidebar } from '@/store/themeConfigSlice';
 | 
			
		||||
import { IRootState } from '@/store';
 | 
			
		||||
 | 
			
		||||
const SidebarToggleButton = () => {
 | 
			
		||||
    const dispatch = useDispatch();
 | 
			
		||||
    const themeConfig = useSelector((state: IRootState) => state.themeConfig);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <button
 | 
			
		||||
            type="button"
 | 
			
		||||
            className="flex h-8 w-8 items-center justify-center rounded-full transition duration-300 hover:bg-gray-500/10 dark:text-white-light dark:hover:bg-dark-light/10"
 | 
			
		||||
            onClick={() => dispatch(toggleSidebar())}
 | 
			
		||||
            // Optional: You might want to hide this button if the sidebar is already open
 | 
			
		||||
            // or show a different icon depending on the sidebar state.
 | 
			
		||||
            // For simplicity, it just toggles.
 | 
			
		||||
        >
 | 
			
		||||
            {/* You can use a generic menu icon here, or condition it based on themeConfig.sidebar */}
 | 
			
		||||
            
 | 
			
		||||
        </button>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SidebarToggleButton;
 | 
			
		||||
@ -62,11 +62,12 @@ const Sidebar = () => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        
 | 
			
		||||
        <div className={semidark ? 'dark' : ''}>
 | 
			
		||||
            <nav
 | 
			
		||||
                className={`sidebar fixed bottom-0 top-0 z-50 h-full min-h-screen w-[260px] shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] transition-all duration-300 ${semidark ? 'text-white-dark' : ''}`}
 | 
			
		||||
            >
 | 
			
		||||
                <div className="h-full bg-white dark:bg-black">
 | 
			
		||||
                <div className="h-full bg-[white] dark:bg-black">
 | 
			
		||||
                    <div className="flex items-center justify-between px-4 py-3">
 | 
			
		||||
                        <Link href="/" className="main-logo flex shrink-0 items-center">
 | 
			
		||||
                            <img className="ml-[5px] w-8 flex-none" src="/assets/images/logo.png" alt="logo" />
 | 
			
		||||
@ -81,8 +82,8 @@ const Sidebar = () => {
 | 
			
		||||
                            <IconCaretsDown className="m-auto rotate-90" />
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <PerfectScrollbar className="relative h-[calc(100vh-80px)]">
 | 
			
		||||
                        <ul className="relative space-y-0.5 p-4 py-0 font-semibold">
 | 
			
		||||
                    <PerfectScrollbar className="relative h-[calc(100vh-80px)] ">
 | 
			
		||||
                        <ul className="relative space-y-0.5 p-4 py-0 font-md">
 | 
			
		||||
                            <h2 className="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
 | 
			
		||||
                                <IconMinus className="hidden h-5 w-4 flex-none" />
 | 
			
		||||
                                <span>Customer</span>
 | 
			
		||||
@ -103,14 +104,6 @@ const Sidebar = () => {
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <Link href="#" className="nav-link group">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
                                        <IconMenuComponents className="shrink-0 group-hover:!text-primary" />
 | 
			
		||||
                                        <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Inverters</span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <button type="button" className={`nav-link group w-full`} onClick={() => toggleMenu('setting')}>
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
@ -139,6 +132,34 @@ const Sidebar = () => {
 | 
			
		||||
                                <IconMinus className="hidden h-5 w-4 flex-none" />
 | 
			
		||||
                                <span>Admin</span>
 | 
			
		||||
                            </h2>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <Link href="/adminDashboard" className="nav-link group">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
                                        <IconMenuComponents className="shrink-0 group-hover:!text-primary" />
 | 
			
		||||
                                        <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Dashboard</span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <Link href="/sites" className="nav-link group">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
                                        <IconMenuComponents className="shrink-0 group-hover:!text-primary" />
 | 
			
		||||
                                        <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Sites</span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <Link href="#" className="nav-link group">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
                                        <IconMenuComponents className="shrink-0 group-hover:!text-primary" />
 | 
			
		||||
                                        <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Devices</span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <h2 className="-mx-4 mb-1 flex items-center px-7 py-3 font-extrabold uppercase dark:bg-opacity-[0.08]">
 | 
			
		||||
                                <IconMinus className="hidden h-5 w-4 flex-none" />
 | 
			
		||||
                                <span>Providers</span>
 | 
			
		||||
                            </h2>
 | 
			
		||||
                            <li className="menu nav-item">
 | 
			
		||||
                                <Link href="#" className="nav-link group">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,31 @@
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import { ComponentType } from "react";
 | 
			
		||||
 | 
			
		||||
const withAuth = (WrappedComponent: React.FC) => {
 | 
			
		||||
    return (props: any) => {
 | 
			
		||||
        const [isAuthenticated, setIsAuthenticated] = useState(false)
 | 
			
		||||
        const router = useRouter()
 | 
			
		||||
const withAuth = <P extends object>(WrappedComponent: ComponentType<P>) => {
 | 
			
		||||
  const WithAuthComponent = (props: P) => {
 | 
			
		||||
    const [isAuthenticated, setIsAuthenticated] = useState(false);
 | 
			
		||||
    const router = useRouter();
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
            const token = localStorage.getItem("token")
 | 
			
		||||
      const token = localStorage.getItem("token");
 | 
			
		||||
 | 
			
		||||
      if (!token) {
 | 
			
		||||
                router.replace("/login") // Redirect to login if no token
 | 
			
		||||
        router.replace("/login");
 | 
			
		||||
      } else {
 | 
			
		||||
                setIsAuthenticated(true)
 | 
			
		||||
        setIsAuthenticated(true);
 | 
			
		||||
      }
 | 
			
		||||
        }, [])
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    if (!isAuthenticated) {
 | 
			
		||||
            return null // Avoid rendering until auth check is done
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        return <WrappedComponent {...props} />
 | 
			
		||||
    return <WrappedComponent {...props} />;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return WithAuthComponent;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withAuth
 | 
			
		||||
export default withAuth;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										738
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										738
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -11,15 +11,18 @@
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@emotion/react": "^11.10.6",
 | 
			
		||||
        "@headlessui/react": "^1.7.8",
 | 
			
		||||
        "@prisma/client": "^6.4.1",
 | 
			
		||||
        "@prisma/client": "^6.8.2",
 | 
			
		||||
        "@reduxjs/toolkit": "^1.9.1",
 | 
			
		||||
        "@tippyjs/react": "^4.2.6",
 | 
			
		||||
        "@types/bcrypt": "^5.0.2",
 | 
			
		||||
        "@types/node": "18.11.18",
 | 
			
		||||
        "@types/react": "18.0.27",
 | 
			
		||||
        "@types/react-dom": "18.0.10",
 | 
			
		||||
        "apexcharts": "^4.5.0",
 | 
			
		||||
        "axios": "^1.7.9",
 | 
			
		||||
        "bcrypt": "^5.1.1",
 | 
			
		||||
        "chart.js": "^4.4.9",
 | 
			
		||||
        "chartjs-plugin-zoom": "^2.2.0",
 | 
			
		||||
        "cookie": "^1.0.2",
 | 
			
		||||
        "eslint": "8.32.0",
 | 
			
		||||
        "eslint-config-next": "13.1.2",
 | 
			
		||||
@ -30,18 +33,21 @@
 | 
			
		||||
        "react": "18.2.0",
 | 
			
		||||
        "react-animate-height": "^3.1.0",
 | 
			
		||||
        "react-apexcharts": "^1.7.0",
 | 
			
		||||
        "react-chartjs-2": "^5.3.0",
 | 
			
		||||
        "react-dom": "18.2.0",
 | 
			
		||||
        "react-hot-toast": "^2.5.2",
 | 
			
		||||
        "react-i18next": "^12.1.5",
 | 
			
		||||
        "react-perfect-scrollbar": "^1.5.8",
 | 
			
		||||
        "react-popper": "^2.3.0",
 | 
			
		||||
        "react-redux": "^8.1.3",
 | 
			
		||||
        "recharts": "^2.15.3",
 | 
			
		||||
        "universal-cookie": "^6.1.1",
 | 
			
		||||
        "yup": "^0.32.11"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@tailwindcss/forms": "^0.5.3",
 | 
			
		||||
        "@tailwindcss/typography": "^0.5.8",
 | 
			
		||||
        "@types/jsonwebtoken": "^9.0.9",
 | 
			
		||||
        "@types/lodash": "^4.14.191",
 | 
			
		||||
        "@types/react-redux": "^7.1.32",
 | 
			
		||||
        "autoprefixer": "^10.4.17",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/images/building.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/building.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 119 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/images/gridtower.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/gridtower.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 134 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/images/solarpanel.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/solarpanel.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 151 KiB  | 
@ -237,7 +237,7 @@ hover:text-primary hover:before:!bg-primary ltr:before:mr-2 rtl:before:ml-2 dark
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .btn-primary {
 | 
			
		||||
        @apply border-primary bg-primary text-white shadow-primary/60;
 | 
			
		||||
        @apply  rounded-3xl px-10 py-2.5 bg-rtyellow-200 text-black text-lg font-medium font-exo2 hover:bg-rtyellow-300;
 | 
			
		||||
    }
 | 
			
		||||
    .btn-outline-primary {
 | 
			
		||||
        @apply border-primary text-primary shadow-none hover:bg-primary hover:text-white;
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ const rotateX = plugin(function ({ addUtilities }) {
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    content: ['./App.tsx', './app/**/*.{js,ts,jsx,tsx}', './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', './src/**/*.{js,ts,jsx,tsx}'],
 | 
			
		||||
    darkMode: 'class',
 | 
			
		||||
@ -16,6 +17,29 @@ module.exports = {
 | 
			
		||||
        },
 | 
			
		||||
        extend: {
 | 
			
		||||
            colors: {
 | 
			
		||||
                rtgray: {
 | 
			
		||||
                    50: '#F9FAFB',
 | 
			
		||||
                    100: '#F3F4F6',
 | 
			
		||||
                    200: '#E6E6EA',
 | 
			
		||||
                    300: '#D3D4D9',
 | 
			
		||||
                    400: '#9EA1AD',
 | 
			
		||||
                    500: '#6D707E',
 | 
			
		||||
                    600: '#4D5261',
 | 
			
		||||
                    700: '#3A3F4E',
 | 
			
		||||
                    800: '#222634',
 | 
			
		||||
                    900: '#141624',
 | 
			
		||||
                    1000: '#080912',
 | 
			
		||||
                    },
 | 
			
		||||
                    rtyellow: {
 | 
			
		||||
                    200: '#FCD913',
 | 
			
		||||
                    300: '#E6C812',
 | 
			
		||||
                    500: '#F2BE03',
 | 
			
		||||
                    600: '#D9A003',
 | 
			
		||||
                    },
 | 
			
		||||
                    rtbrown: {
 | 
			
		||||
                    800: '#301E03',
 | 
			
		||||
                    },
 | 
			
		||||
                    rtwhite: '#FFFFFF',
 | 
			
		||||
                primary: {
 | 
			
		||||
                    DEFAULT: '#fcd913',
 | 
			
		||||
                    light: '#eaf1ff',
 | 
			
		||||
@ -64,6 +88,9 @@ module.exports = {
 | 
			
		||||
        },
 | 
			
		||||
        fontFamily: {
 | 
			
		||||
            nunito: ['var(--font-nunito)'],
 | 
			
		||||
            exo2: ['var(--font-exo2)'],
 | 
			
		||||
            sans: ['var(--font-sans)'],
 | 
			
		||||
            mono: ['var(--font-mono)'],
 | 
			
		||||
        },
 | 
			
		||||
            spacing: {
 | 
			
		||||
                4.5: '18px',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								types/SiteData.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								types/SiteData.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
// types/siteData.ts
 | 
			
		||||
export type SiteName = 'Site A' | 'Site B' | 'Site C';
 | 
			
		||||
 | 
			
		||||
export interface SiteDetails {
 | 
			
		||||
    location: string;
 | 
			
		||||
    inverterProvider: string;
 | 
			
		||||
    emergencyContact: string;
 | 
			
		||||
    lastSyncTimestamp: string;
 | 
			
		||||
    consumptionData: number[];
 | 
			
		||||
    generationData: number[];
 | 
			
		||||
    // New properties for SystemOverview
 | 
			
		||||
    systemStatus: string; // e.g., "Normal", "Faulty"
 | 
			
		||||
    temperature: string; // e.g., "35°C"
 | 
			
		||||
    solarPower: number; // Power generated by solar (kW)
 | 
			
		||||
    realTimePower: number; // Real-time power used (kW)
 | 
			
		||||
    installedPower: number; // Installed capacity (kWp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mockSiteData: Record<SiteName, SiteDetails> = {
 | 
			
		||||
    'Site A': {
 | 
			
		||||
        location: 'Petaling Jaya, Selangor',
 | 
			
		||||
        inverterProvider: 'SolarEdge',
 | 
			
		||||
        emergencyContact: '+60 12-345 6789',
 | 
			
		||||
        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],
 | 
			
		||||
        generationData: [80, 90, 95, 105, 110, 120, 115, 150, 130, 160, 120, 140, 120, 140, 130, 140, 150, 155, 140],
 | 
			
		||||
        systemStatus: 'Normal', // Added
 | 
			
		||||
        temperature: '35°C', // Added
 | 
			
		||||
        solarPower: 108.4, // Added
 | 
			
		||||
        realTimePower: 108.4, // Added
 | 
			
		||||
        installedPower: 174.9, // Added
 | 
			
		||||
    },
 | 
			
		||||
    'Site B': {
 | 
			
		||||
        location: 'Kuala Lumpur, Wilayah Persekutuan',
 | 
			
		||||
        inverterProvider: 'Huawei',
 | 
			
		||||
        emergencyContact: '+60 19-876 5432',
 | 
			
		||||
        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],
 | 
			
		||||
        generationData: [70, 80, 85, 95, 100, 110, 105, 140, 120, 150, 110, 130, 110, 130, 120, 130, 140, 145, 130],
 | 
			
		||||
        systemStatus: 'Normal', // Added
 | 
			
		||||
        temperature: '32°C', // Added
 | 
			
		||||
        solarPower: 95.2, // Added
 | 
			
		||||
        realTimePower: 95.2, // Added
 | 
			
		||||
        installedPower: 150.0, // Added
 | 
			
		||||
    },
 | 
			
		||||
    'Site C': {
 | 
			
		||||
        location: 'Johor Bahru, Johor',
 | 
			
		||||
        inverterProvider: 'Enphase',
 | 
			
		||||
        emergencyContact: '+60 13-555 1234',
 | 
			
		||||
        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],
 | 
			
		||||
        generationData: [50, 60, 65, 75, 80, 90, 85, 100, 90, 110, 80, 90, 80, 90, 80, 90, 100, 105, 90],
 | 
			
		||||
        systemStatus: 'Faulty', // Added
 | 
			
		||||
        temperature: '30°C', // Added
 | 
			
		||||
        solarPower: 25.0, // Added (lower due to fault)
 | 
			
		||||
        realTimePower: 70.0, // Added
 | 
			
		||||
        installedPower: 120.0, // Added
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user