'use client'; import { useState, useEffect, useMemo, useRef } from 'react'; import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import SiteSelector from '@/components/dashboards/SiteSelector'; import SiteStatus from '@/components/dashboards/SiteStatus'; import DashboardLayout from './dashlayout'; import html2canvas from 'html2canvas'; import jsPDF from 'jspdf'; import dynamic from 'next/dynamic'; import { fetchPowerTimeseries } from '@/app/utils/api'; import KpiTop from '@/components/dashboards/kpitop'; import KpiBottom from '@/components/dashboards/kpibottom'; import { formatAddress } from '@/app/utils/formatAddress'; import { formatCrmTimestamp } from '@/app/utils/datetime'; const EnergyLineChart = dynamic(() => import('@/components/dashboards/EnergyLineChart'), { ssr: false }); const MonthlyBarChart = dynamic(() => import('@/components/dashboards/MonthlyBarChart'), { ssr: false }); type MonthlyKPI = { site: string; month: string; yield_kwh: number | null; consumption_kwh: number | null; grid_draw_kwh: number | null; efficiency: number | null; peak_demand_kw: number | null; avg_power_factor: number | null; load_factor: number | null; error?: string; }; type CrmProject = { name: string; // e.g. PROJ-0008 <-- use as siteId project_name: string; status?: string; percent_complete?: number | null; owner?: string | null; modified?: string | null; customer?: string | null; project_type?: string | null; custom_address?: string | null; custom_email?: string | null; custom_mobile_phone_no?: string | null; }; const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8000'; const AdminDashboard = () => { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); // --- NEW: load CRM projects dynamically --- const [sites, setSites] = useState([]); const [sitesLoading, setSitesLoading] = useState(true); const [sitesError, setSitesError] = useState(null); useEffect(() => { setSitesLoading(true); fetch(`${API}/crm/projects?limit=0`) .then(r => r.json()) .then(json => setSites(json?.data ?? [])) .catch(setSitesError) .finally(() => setSitesLoading(false)); }, []); // The canonical siteId is the CRM Project "name" (e.g., PROJ-0008) const siteParam = searchParams?.get('site') || null; const [selectedSiteId, setSelectedSiteId] = useState(siteParam); // Keep query param <-> state in sync useEffect(() => { if ((siteParam || null) !== selectedSiteId) { setSelectedSiteId(siteParam); } }, [siteParam]); // eslint-disable-line // Default to the first site when loaded useEffect(() => { if (!selectedSiteId && sites.length) { setSelectedSiteId(sites[0].name); router.replace(`${pathname}?site=${encodeURIComponent(sites[0].name)}`); } }, [sites, selectedSiteId, pathname, router]); // Current selected CRM project const selectedProject: CrmProject | null = useMemo( () => sites.find(s => s.name === selectedSiteId) ?? null, [sites, selectedSiteId] ); // --- FIX: declare currentMonth BEFORE it’s used --- const currentMonth = useMemo(() => new Date().toISOString().slice(0, 7), []); // --- Time-series state (unchanged) --- const [timeSeriesData, setTimeSeriesData] = useState<{ consumption: { time: string; value: number }[]; generation: { time: string; value: number }[]; }>({ consumption: [], generation: [] }); // Fetch today’s timeseries for selected siteId (from CRM) useEffect(() => { if (!selectedSiteId) return; const fetchData = async () => { const today = new Date(); const yyyyMMdd = today.toISOString().split('T')[0]; const start = `${yyyyMMdd}T00:00:00+08:00`; const end = `${yyyyMMdd}T23:59:59+08:00`; try { const raw = await fetchPowerTimeseries(selectedSiteId, start, end); const consumption = raw.consumption.map((d: any) => ({ time: d.time, value: d.value })); const generation = raw.generation.map((d: any) => ({ time: d.time, value: d.value })); setTimeSeriesData({ consumption, generation }); } catch (error) { console.error('Failed to fetch power time series:', error); } }; fetchData(); }, [selectedSiteId]); // --- KPI monthly (uses your FastAPI) --- const [kpi, setKpi] = useState(null); useEffect(() => { if (!selectedSiteId) return; const url = `${API}/kpi/monthly?site=${encodeURIComponent(selectedSiteId)}&month=${currentMonth}`; fetch(url).then(r => r.json()).then(setKpi).catch(console.error); }, [selectedSiteId, currentMonth]); // derived values with safe fallbacks const yieldKwh = kpi?.yield_kwh ?? 0; const consumptionKwh = kpi?.consumption_kwh ?? 0; const gridDrawKwh = kpi?.grid_draw_kwh ?? Math.max(0, consumptionKwh - yieldKwh); const efficiencyPct = (kpi?.efficiency ?? 0) * 100; const powerFactor = kpi?.avg_power_factor ?? 0; const loadFactor = (kpi?.load_factor ?? 0); // Update URL when site is changed manually (now expects a siteId/Project.name) const handleSiteChange = (newSiteId: string) => { setSelectedSiteId(newSiteId); const newUrl = `${pathname}?site=${encodeURIComponent(newSiteId)}`; router.push(newUrl); }; const locationFormatted = useMemo(() => { const raw = selectedProject?.custom_address ?? ''; if (!raw) return 'N/A'; return formatAddress(raw).multiLine; // pretty, multi-line version }, [selectedProject?.custom_address]); const lastSyncFormatted = useMemo( () => formatCrmTimestamp(selectedProject?.modified, { includeSeconds: true }), [selectedProject?.modified] ); // Adapt CRM project -> SiteStatus props const currentSiteDetails = { location: locationFormatted, // <- formatted! inverterProvider: selectedProject?.project_type || 'N/A', emergencyContact: selectedProject?.custom_mobile_phone_no || selectedProject?.custom_email || selectedProject?.customer || 'N/A', lastSyncTimestamp: lastSyncFormatted || 'N/A', }; const energyChartRef = useRef(null); const monthlyChartRef = useRef(null); const handlePDFExport = async () => { const doc = new jsPDF('p', 'mm', '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; const canvas = await html2canvas(chart.ref.current, { scale: 2 }); const imgData = canvas.toDataURL('image/png'); const imgProps = doc.getImageProperties(imgData); const pdfWidth = doc.internal.pageSize.getWidth() - 20; const imgHeight = (imgProps.height * pdfWidth) / imgProps.width; doc.setFontSize(14); doc.text(chart.title, 10, yOffset); yOffset += 6; if (yOffset + imgHeight > doc.internal.pageSize.getHeight()) { doc.addPage(); yOffset = 10; } doc.addImage(imgData, 'PNG', 10, yOffset, pdfWidth, imgHeight); yOffset += imgHeight + 10; } doc.save('dashboard_charts.pdf'); }; if (sitesLoading) { return (
Loading sites…
); } if (sitesError) { return (
Failed to load sites from CRM.
); } if (!selectedProject) { return (
No site selected.
); } // Build selector options from CRM const siteOptions = sites.map(s => ({ label: s.project_name || s.name, // nice display value: s.name, // siteId used everywhere })); return (

Admin Dashboard

{/* UPDATE SiteSelector to accept these props */} {/* UPDATE SiteStatus to accept siteId & dynamic fields */}
{/* TOP 3 CARDS */}
{/* BOTTOM 3 PANELS */}
} right={
{(kpi?.peak_demand_kw ?? 0).toFixed(2)} kW
} />
); }; export default AdminDashboard;