"use client"; import { useState, useCallback, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, LineChart, Line, CartesianGrid, Legend, } from "recharts"; import { getAvailableProfiles, getProfileStats, getSolarProfile, getWindProfile, profileCsvUrl, type ProfileStats, } from "@/lib/api"; interface ProfileData { solar: Record; // { locationId: [8760 values] } wind: Record; stats: Record; } const LOCATIONS = [ { id: "GJ", name: "Gujarat" }, { id: "KA", name: "Karnataka" }, { id: "RJ", name: "Rajasthan" }, ]; const SOLAR_COLOR = "#f97316"; // orange-500 const WIND_COLOR = "#3b82f6"; // blue-500 const UPLOAD_COLOR = "#8b5cf6"; // violet-500 interface StatsCardProps { label: string; value: string; color?: string; } function StatsCard({ label, value, color }: StatsCardProps) { return (
{label}
{value}
); } // Custom tooltip for charts function CustomTooltip({ active, payload, label }: { active?: boolean; payload?: { value: number; name: string }[]; label?: string }) { if (!active || !payload?.length) return null; return (

{label}

{payload.map((entry, i) => (
{entry.name}: {(entry.value * 100).toFixed(2)}%
))}
); } export function ProfileViewer() { const [isOpen, setIsOpen] = useState(false); const [activeTab, setActiveTab] = useState<"solar" | "wind" | "compare">("solar"); const [uploadedProfiles, setUploadedProfiles] = useState<{ solar?: number[]; wind?: number[]; filename?: string; }>({}); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [selectedFile, setSelectedFile] = useState<{ name: string; path: string; data: string[][]; headers: string[]; } | null>(null); const [isLoadingFile, setIsLoadingFile] = useState(false); const API_BASE = typeof window !== 'undefined' ? ((window as any).NEXT_PUBLIC_API_URL || 'http://localhost:8000') : 'http://localhost:8000'; async function fetchAndDisplayFile(path: string, name: string) { setIsLoadingFile(true); try { const res = await fetch(`${API_BASE}${path}`); const text = await res.text(); const lines = text.trim().split('\n'); const headers = lines[0].split(','); const data = lines.slice(1).map(line => line.split(',')); setSelectedFile({ name, path, data, headers }); } catch (err) { console.error('Failed to fetch file:', err); } finally { setIsLoadingFile(false); } } // Calculate stats for a profile const calculateStats = (data: number[]) => { const sorted = [...data].sort((a, b) => a - b); const sum = data.reduce((a, b) => a + b, 0); return { avg: sum / data.length, max: Math.max(...data), min: Math.min(...data), median: sorted[Math.floor(sorted.length / 2)], }; }; // Handle file upload const handleFileUpload = useCallback(async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; setIsUploading(true); setUploadError(null); try { const text = await file.text(); const lines = text.trim().split("\n"); if (lines.length < 2) { throw new Error("File must have at least a header row and one data row"); } // Try to detect format const header = lines[0].toLowerCase(); let parsedData: number[] = []; if (header.includes("%_generation")) { // New format: find the %_generation column const cols = header.split(","); const colIdx = cols.indexOf("%_generation"); if (colIdx === -1) throw new Error("Could not find %_generation column"); parsedData = lines.slice(1).map((line) => { const vals = line.split(","); let val = parseFloat(vals[colIdx]); if (isNaN(val)) return 0; if (val > 1) val = val / 100; // Convert % to fraction return val; }); } else if (header.includes("irradiance") || header.includes("wind_speed") || header.includes("e_grid")) { // Old format: usually second column parsedData = lines.slice(1).map((line) => { const vals = line.split(","); let val = parseFloat(vals[1]); if (isNaN(val)) return 0; // Handle different scales if (val > 1) val = val / 1000; // Convert kW to MW if (val > 1) val = val / 100; // Convert % to fraction return val; }); } else { // Generic: just take all numeric values from first column parsedData = lines.slice(1).map((line) => { const vals = line.split(","); let val = parseFloat(vals[0]); if (isNaN(val)) return 0; return val; }); } // Validate 8760 values if (parsedData.length !== 8760) { throw new Error(`Expected 8760 values, got ${parsedData.length}`); } // Determine if solar or wind based on average value (solar typically higher during day) const isSolar = parsedData.reduce((a, b, i) => { // Solar has high values during midday hours (indices 6-18 of each day) const hourOfDay = i % 24; if (hourOfDay >= 6 && hourOfDay <= 18) return a + b; return a; }, 0) / parsedData.reduce((a, b) => a + b, 0) > 0.7; setUploadedProfiles({ solar: isSolar ? parsedData : undefined, wind: !isSolar ? parsedData : undefined, filename: file.name, }); } catch (err) { setUploadError(err instanceof Error ? err.message : "Failed to parse file"); } finally { setIsUploading(false); } }, []); // Generate monthly averages for chart const getMonthlyAverages = (data: number[], isSolar: boolean) => { const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const monthNames = ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar"]; const monthPositions = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3]; return monthNames.map((name, i) => { const month = monthPositions[i]; const start = MONTH_DAYS.slice(0, month - 1).reduce((a, d) => a + d, 0) * 24; const days = MONTH_DAYS[month - 1]; const monthData = data.slice(start, start + days * 24); const avg = monthData.reduce((a, b) => a + b, 0) / monthData.length; return { name, avg: avg * 100 }; }); }; // Generate hourly average profile for a typical day const getHourlyAverage = (data: number[]) => { const hourly = new Array(24).fill(0); for (let i = 0; i < 8760; i++) { hourly[i % 24] += data[i]; } return hourly.map((v) => v / 365); }; return ( <> {/* Button to open the modal */} {/* Modal */} {isOpen && (
{/* Backdrop */}
setIsOpen(false)} /> {/* Modal content */}
{/* Header */}

File Reference Library

{/* Tabs */}
{/* Upload section */}
{uploadError && ( {uploadError} )}
{/* Content */}
{/* Bundled profiles section */}

Bundled Reference Files

{[ { name: "Solar - Gujarat", path: "/api/profiles/solar/GJ", type: "solar", id: "solar_gj" }, { name: "Solar - Karnataka", path: "/api/profiles/solar/KA", type: "solar", id: "solar_ka" }, { name: "Solar - Rajasthan", path: "/api/profiles/solar/RJ", type: "solar", id: "solar_rj" }, { name: "Wind - Gujarat", path: "/api/profiles/wind/GJ", type: "wind", id: "wind_gj" }, { name: "Wind - Karnataka", path: "/api/profiles/wind/KA", type: "wind", id: "wind_ka" }, { name: "Wind - Rajasthan", path: "/api/profiles/wind/RJ", type: "wind", id: "wind_rj" }, ].map((profile) => ( ))}
{/* File preview section */} {selectedFile && (
{selectedFile.name} ({selectedFile.data.length} rows)
Download CSV
{selectedFile.data.slice(0, 100).map((row: string[], i: number) => ( ))}
Hour Date Month Day Hour of Day % Generation
{row[0]} {row[1]} {row[2]} {row[3]} {row[4]} {row[5]}
{selectedFile.data.length > 100 && (
Showing first 100 of {selectedFile.data.length} rows. Download CSV for full data.
)}
)} {activeTab === "solar" && (

Monthly Average Solar Irradiance

{ // Mock data for demonstration - in real app this would come from API const mockData = Array(8760).fill(0).map((_, i) => { const hour = i % 24; const dayOfYear = Math.floor(i / 24); // Simple sinusoidal approximation const solarNoon = Math.sin((hour - 6) * Math.PI / 12); const seasonFactor = Math.sin((dayOfYear - 80) * 2 * Math.PI / 365); return Math.max(0, solarNoon * (0.8 + 0.2 * seasonFactor)); }); const monthly = getMonthlyAverages(mockData, true); const idx = ["GJ", "KA", "RJ"].indexOf(loc.id); return { name: loc.name, Jan: monthly[9]?.avg || 0, Feb: monthly[10]?.avg || 0, Mar: monthly[11]?.avg || 0, Apr: monthly[0]?.avg || 0, May: monthly[1]?.avg || 0, Jun: monthly[2]?.avg || 0, Jul: monthly[3]?.avg || 0, Aug: monthly[4]?.avg || 0, Sep: monthly[5]?.avg || 0, Oct: monthly[6]?.avg || 0, Nov: monthly[7]?.avg || 0, Dec: monthly[8]?.avg || 0, [idx]: 0, }; })}> } />
{uploadedProfiles.solar && (

Your Uploaded Solar Profile

{(() => { const stats = calculateStats(uploadedProfiles.solar!); return ( <> ); })()}
)}
)} {activeTab === "wind" && (

Monthly Average Wind Speed (m/s)

{uploadedProfiles.wind && (

Your Uploaded Wind Profile

{(() => { const stats = calculateStats(uploadedProfiles.wind!); return ( <> ); })()}
)}
)} {activeTab === "compare" && (

Solar vs Wind CUF (Typical Daily Pattern)

{ const hour = i % 24; const solarValue = Math.max(0, Math.sin((hour - 6) * Math.PI / 12)); const windValue = 0.3 + 0.2 * Math.sin(i * Math.PI / 12) + 0.1 * Math.random(); return (solarValue + windValue) / 2; }) ).map((v, i) => ({ hour: `${String(i).padStart(2, '0')}:00`, Solar: v, Wind: 0.3 + 0.1 * Math.sin(i * Math.PI / 12) }))}> `${(v * 100).toFixed(0)}%`} /> } />
{uploadedProfiles.solar && uploadedProfiles.wind && (

Your Uploaded Profiles Comparison

{(() => { const solarStats = calculateStats(uploadedProfiles.solar!); const windStats = calculateStats(uploadedProfiles.wind!); return ( <> ); })()}
)}
)}
)} ); }