/* ============================================================
Enterprise mode — preset scenarios + portfolio dashboard
============================================================ */
/* global React, EnterpriseMapView, Icon, BarChart, Histogram, AzimuthRose, Sparkline */
const { useState: useStateE, useMemo: useMemoE, useEffect: useEffectE } = React;
const utilsE = window.LEPTON_UTILS;
function ScenarioCards({ scenarios, activeId, onSelect }) {
return (
Portfolio scenario
Pre-warmed · zero live API calls
{scenarios.map(s => (
onSelect(s.id)}>
{s.title}
{s.subtitle} · {s.partner}
{s.count} bldgs
))}
Cached · 100%
);
}
function KpiStrip({ scenario }) {
const buildings = scenario.buildings;
const totalKwp = buildings.reduce((s, b) => s + b.kWp, 0);
const totalGwh = buildings.reduce((s, b) => s + b.yearlyKwh, 0) / 1e6;
const totalCo2 = buildings.reduce((s, b) => s + b.co2, 0);
const totalCapex = buildings.reduce((s, b) => s + b.capex, 0);
const eligible = scenario.suryaGharEligible ? buildings.filter(b => b.kWp <= 10).length : 0;
const avgPayback = (buildings.reduce((s, b) => s + b.payback, 0) / buildings.length).toFixed(1);
// sparklines per kpi — sorted buildings by metric
const kWpSeries = [...buildings].sort((a, b) => a.kWp - b.kWp).map(b => b.kWp);
const paybackSeries = [...buildings].sort((a, b) => a.payback - b.payback).map(b => b.payback);
return (
Total installable
{utilsE.NUM(totalKwp, 0)}kWp
● {buildings.length} buildings curated
Aggregate generation
{totalGwh.toFixed(2)}GWh/yr
{utilsE.NUM(Math.round(totalGwh * 1000), 0)} MWh · 25y
CO₂ offset / yr
{(totalCo2 / 1000).toFixed(1)}t
{utilsE.NUM(utilsE.treesEquivalent(totalCo2))} trees equiv.
CAPEX opportunity
{utilsE.INR(totalCapex)}
avg ₹{Math.round(totalCapex/buildings.length/1000)}k / site
Avg payback
{avgPayback}yrs
Surya Ghar eligible
{scenario.suryaGharEligible ? `${Math.round(eligible/buildings.length*100)}%` : "—"}
{scenario.suryaGharEligible ? `${eligible}/${buildings.length} residential` : "non-residential scenario"}
);
}
function RankedTable({ scenario, selectedId, onSelect, hoveredId, onHover, sort, setSort, colorMode }) {
let rows = [...scenario.buildings];
rows.sort((a, b) => {
const dir = sort.dir === "asc" ? 1 : -1;
return (a[sort.key] - b[sort.key]) * dir;
});
const maxKwp = Math.max(...rows.map(b => b.kWp));
const headerCell = (key, label, num = false) => (
setSort({ key, dir: sort.key === key && sort.dir === "desc" ? "asc" : "desc" })}
>
{label}
{sort.key === key ? (sort.dir === "asc" ? "↑" : "↓") : "↕"}
|
);
return (
| building |
cluster |
{headerCell("kWp", "kWp", true)}
{headerCell("yearlyKwh", "MWh / yr", true)}
{headerCell("payback", "payback", true)}
{headerCell("co2", "CO₂ t/yr", true)}
{headerCell("suitability", "fit", true)}
quality |
{rows.slice(0, 60).map(b => (
onHover(b.id)}
onMouseLeave={() => onHover(null)}
onClick={() => onSelect(b.id)}
>
| {b.id} |
{b.cluster} {b.clusterName.split(" ")[0]} |
{b.kWp.toFixed(1)}
|
{(b.yearlyKwh / 1000).toFixed(1)} |
6 ? "warn" : "")}>{b.payback} |
{(b.co2 / 1000).toFixed(1)} |
{b.suitability} |
{b.imageryQuality} |
))}
);
}
function ChartsRow({ scenario }) {
// kWp histogram
const kwps = scenario.buildings.map(b => b.kWp);
const max = Math.max(...kwps);
const binCount = 10;
const bins = new Array(binCount).fill(0);
const binLabels = [];
for (let i = 0; i < binCount; i++) {
const lo = (max / binCount) * i;
const hi = (max / binCount) * (i + 1);
binLabels.push(`${Math.round(lo)}`);
}
kwps.forEach(v => {
const b = Math.min(binCount - 1, Math.floor(v / (max / binCount)));
bins[b]++;
});
const histData = bins.map((v, i) => ({ label: binLabels[i], value: v }));
return (
kWp distribution
peak at {binLabels[bins.indexOf(Math.max(...bins))]} kWp
n={scenario.buildings.length}
Azimuth rose
south-bias = better yield (north India)
flat-roof excluded
);
}
/* ============================================================
Enterprise main
============================================================ */
function EnterpriseMode() {
const data = window.useLeptonData();
const SCENARIOS = data.SCENARIOS;
const [scenarioId, setScenarioId] = useStateE("mundra");
const [colorMode, setColorMode] = useStateE("kwp");
const [selectedId, setSelectedId] = useStateE(null);
const [hoveredId, setHoveredId] = useStateE(null);
const [sort, setSort] = useStateE({ key: "kWp", dir: "desc" });
// Lazy-load full scenario detail (buildings[]) on selection.
useEffectE(() => {
if (scenarioId && !data.SCENARIO_DETAILS[scenarioId] && data.loading.scenario !== scenarioId) {
window.LEPTON_LOAD.loadScenario(scenarioId).catch(() => {});
}
}, [scenarioId, data.SCENARIO_DETAILS[scenarioId], data.loading.scenario]);
const summary = SCENARIOS.find(s => s.id === scenarioId);
const detail = data.SCENARIO_DETAILS[scenarioId];
const scenario = detail || summary;
// pick a default selected once scenario detail loads
useEffectE(() => {
if (detail && detail.buildings && detail.buildings.length) {
const sorted = [...detail.buildings].sort((a, b) => b.kWp - a.kWp);
setSelectedId(sorted[0].id);
}
}, [scenarioId, !!detail]);
if (!SCENARIOS.length) {
return (
);
}
if (!scenario || !scenario.buildings) {
return (
{data.loading.scenario === scenarioId
? `Pre-warming ${summary?.title} (${summary?.count || ""} buildings)… first run only.`
: (data.error.scenario || "Loading scenario…")}
);
}
const selected = scenario.buildings.find(b => b.id === selectedId);
return (
{/* color mode controls */}
Color by
{/* Legend */}
{colorMode === "kwp" ? "kWp" : colorMode === "payback" ? "Payback (yrs)" : "Suitability"}
{colorMode === "payback" ? "long" : "low"}
{colorMode === "payback" ? "short" : "high"}
{scenario.buildings.length} buildings · BASE quality
{/* selected building inspector */}
{selected && (
{selected.id} · {selected.clusterName}
kWp{selected.kWp}
annual{(selected.yearlyKwh/1000).toFixed(2)} MWh
azimuth · tilt{selected.azimuth}° · {selected.tilt}°
payback{selected.payback} yrs
CAPEX{utilsE.INR(selected.capex)}
area{selected.areaM2} m²
)}
{scenario.center[0].toFixed(4)}°N · {scenario.center[1].toFixed(4)}°E
{scenario.tariffNote} · ₹{scenario.tariff.toFixed(2)}/kWh
Ranked roofs · click to drop into homeowner view
{scenario.buildings.length} rows
sort {sort.key} {sort.dir}
);
}
window.EnterpriseMode = EnterpriseMode;