215 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
'use client';
 | 
						|
 | 
						|
import { useState, useEffect, useRef } from 'react';
 | 
						|
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
 | 
						|
import SiteSelector from '@/components/dashboards/SiteSelector';
 | 
						|
import SiteStatus from '@/components/dashboards/SiteStatus';
 | 
						|
import KPI_Table from '@/components/dashboards/KPIStatus';
 | 
						|
import DashboardLayout from './dashlayout';
 | 
						|
import html2canvas from 'html2canvas';
 | 
						|
import jsPDF from 'jspdf';
 | 
						|
import dynamic from 'next/dynamic';
 | 
						|
import { fetchPowerTimeseries } from '@/app/utils/api';
 | 
						|
 | 
						|
const EnergyLineChart = dynamic(() => import('@/components/dashboards/EnergyLineChart'), {
 | 
						|
  ssr: false,
 | 
						|
});
 | 
						|
 | 
						|
const MonthlyBarChart = dynamic(() => import('@/components/dashboards/MonthlyBarChart'), {
 | 
						|
  ssr: false,
 | 
						|
});
 | 
						|
 | 
						|
import { SiteName, SiteDetails, mockSiteData } from '@/types/SiteData';
 | 
						|
 | 
						|
const AdminDashboard = () => {
 | 
						|
  const router = useRouter();
 | 
						|
  const pathname = usePathname();
 | 
						|
  const searchParams = useSearchParams();
 | 
						|
  const siteIdMap: Record<SiteName, string> = {
 | 
						|
  'Site A': 'site_01',
 | 
						|
  'Site B': 'site_02',
 | 
						|
  'Site C': 'site_03',
 | 
						|
};
 | 
						|
 | 
						|
  const siteParam = searchParams?.get('site');
 | 
						|
  const validSiteNames: SiteName[] = ['Site A', 'Site B', 'Site C'];
 | 
						|
 | 
						|
  const [selectedSite, setSelectedSite] = useState<SiteName>(() => {
 | 
						|
    if (siteParam && validSiteNames.includes(siteParam as SiteName)) {
 | 
						|
      return siteParam as SiteName;
 | 
						|
    }
 | 
						|
    return 'Site A';
 | 
						|
  });
 | 
						|
 | 
						|
  // Keep siteParam and selectedSite in sync
 | 
						|
  useEffect(() => {
 | 
						|
    if (
 | 
						|
      siteParam &&
 | 
						|
      validSiteNames.includes(siteParam as SiteName) &&
 | 
						|
      siteParam !== selectedSite
 | 
						|
    ) {
 | 
						|
      setSelectedSite(siteParam as SiteName);
 | 
						|
    }
 | 
						|
  }, [siteParam, selectedSite]);
 | 
						|
 | 
						|
  const [timeSeriesData, setTimeSeriesData] = useState<{
 | 
						|
    consumption: { time: string; value: number }[];
 | 
						|
    generation: { time: string; value: number }[];
 | 
						|
  }>({ consumption: [], generation: [] });
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
  const fetchData = async () => {
 | 
						|
 | 
						|
  const siteId = siteIdMap[selectedSite];
 | 
						|
  const today = new Date();
 | 
						|
 | 
						|
  // Format to YYYY-MM-DD
 | 
						|
  const yyyyMMdd = today.toISOString().split('T')[0];
 | 
						|
 | 
						|
  // Append Malaysia's +08:00 time zone manually
 | 
						|
  const start = `${yyyyMMdd}T00:00:00+08:00`;
 | 
						|
  const end = `${yyyyMMdd}T23:59:59+08:00`;
 | 
						|
  
 | 
						|
 | 
						|
    try {
 | 
						|
      const raw = await fetchPowerTimeseries(siteId, start, end);
 | 
						|
 | 
						|
    const consumption = raw.consumption.map(d => ({
 | 
						|
      time: d.time,
 | 
						|
      value: d.value,
 | 
						|
    }));
 | 
						|
 | 
						|
    const generation = raw.generation.map(d => ({
 | 
						|
      time: d.time,
 | 
						|
      value: d.value,
 | 
						|
    }));  
 | 
						|
 | 
						|
    setTimeSeriesData({ consumption, generation });
 | 
						|
 | 
						|
    } catch (error) {
 | 
						|
      console.error('Failed to fetch power time series:', error);
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  fetchData();
 | 
						|
}, [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] || {
 | 
						|
    location: 'N/A',
 | 
						|
    inverterProvider: 'N/A',
 | 
						|
    emergencyContact: 'N/A',
 | 
						|
    lastSyncTimestamp: 'N/A',
 | 
						|
    consumptionData: [],
 | 
						|
    generationData: [],
 | 
						|
    systemStatus: 'N/A',
 | 
						|
    temperature: 'N/A',
 | 
						|
    solarPower: 0,
 | 
						|
    realTimePower: 0,
 | 
						|
    installedPower: 0,
 | 
						|
  };
 | 
						|
 | 
						|
  const handleCSVExport = () => {
 | 
						|
    alert('Exported raw data to CSV (mock)');
 | 
						|
  };
 | 
						|
 | 
						|
  const energyChartRef = useRef(null);
 | 
						|
  const monthlyChartRef = useRef(null);
 | 
						|
 | 
						|
  const handlePDFExport = async () => {
 | 
						|
  const doc = new jsPDF('p', 'mm', 'a4'); // portrait, millimeters, A4
 | 
						|
  const chartRefs = [
 | 
						|
    { ref: energyChartRef, title: 'Energy Line Chart' },
 | 
						|
    { ref: monthlyChartRef, title: 'Monthly Energy Yield' }
 | 
						|
  ];
 | 
						|
 | 
						|
  let yOffset = 10;
 | 
						|
 | 
						|
  for (const chart of chartRefs) {
 | 
						|
    if (!chart.ref.current) continue;
 | 
						|
 | 
						|
    // Capture chart as image
 | 
						|
    const canvas = await html2canvas(chart.ref.current, {
 | 
						|
      scale: 2, // Higher scale for better resolution
 | 
						|
    });
 | 
						|
 | 
						|
    const imgData = canvas.toDataURL('image/png');
 | 
						|
    const imgProps = doc.getImageProperties(imgData);
 | 
						|
 | 
						|
    const pdfWidth = doc.internal.pageSize.getWidth() - 20; // 10 margin each side
 | 
						|
    const imgHeight = (imgProps.height * pdfWidth) / imgProps.width;
 | 
						|
 | 
						|
    // Add title and image
 | 
						|
    doc.setFontSize(14);
 | 
						|
    doc.text(chart.title, 10, yOffset);
 | 
						|
    yOffset += 6; // Space between title and chart
 | 
						|
 | 
						|
    // If content will overflow page, add a new page
 | 
						|
    if (yOffset + imgHeight > doc.internal.pageSize.getHeight()) {
 | 
						|
      doc.addPage();
 | 
						|
      yOffset = 10;
 | 
						|
    }
 | 
						|
 | 
						|
    doc.addImage(imgData, 'PNG', 10, yOffset, pdfWidth, imgHeight);
 | 
						|
    yOffset += imgHeight + 10; // Update offset for next chart
 | 
						|
  }
 | 
						|
 | 
						|
  doc.save('dashboard_charts.pdf');
 | 
						|
};
 | 
						|
  const currentMonth = new Date().toISOString().slice(0, 7); // "YYYY-MM"
 | 
						|
 | 
						|
  return (
 | 
						|
    <DashboardLayout>
 | 
						|
      <div className="px-6 space-y-6">
 | 
						|
        <h1 className="text-lg font-semibold">Admin Dashboard</h1>
 | 
						|
 | 
						|
        <div className="grid md:grid-cols-2 gap-6">
 | 
						|
          <div className="space-y-4">
 | 
						|
            <SiteSelector
 | 
						|
              selectedSite={selectedSite}
 | 
						|
              setSelectedSite={handleSiteChange}
 | 
						|
            />
 | 
						|
            <SiteStatus
 | 
						|
              selectedSite={selectedSite}
 | 
						|
              location={currentSiteDetails.location}
 | 
						|
              inverterProvider={currentSiteDetails.inverterProvider}
 | 
						|
              emergencyContact={currentSiteDetails.emergencyContact}
 | 
						|
              lastSyncTimestamp={currentSiteDetails.lastSyncTimestamp}
 | 
						|
            />
 | 
						|
          </div>
 | 
						|
 | 
						|
          <div>
 | 
						|
          <KPI_Table siteId={siteIdMap[selectedSite]} month={currentMonth} />
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div className="grid md:grid-cols-2 gap-6 lg:flex-col justify-center">
 | 
						|
          <div ref={energyChartRef} className="pb-5">
 | 
						|
            <EnergyLineChart siteId={siteIdMap[selectedSite]} />
 | 
						|
 | 
						|
          </div>
 | 
						|
          <div ref={monthlyChartRef} className="pb-5">
 | 
						|
            <MonthlyBarChart siteId={siteIdMap[selectedSite]} />
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div className="flex flex-col md:flex-row gap-4 justify-center">
 | 
						|
          <button onClick={handlePDFExport} className="text-sm lg:text-lg btn-primary">
 | 
						|
            Export Chart Images to PDF
 | 
						|
          </button>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
    </DashboardLayout>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export default AdminDashboard;
 | 
						|
 | 
						|
 |