Make KPI summary cards clickable - navigate to relevant sheets
Some checks are pending
CI / Engine — lint / typecheck / test (push) Waiting to run
CI / API — lint / typecheck / test (push) Waiting to run
CI / Web — typecheck / lint / build (push) Waiting to run

- Added onClick prop to KpiCard component
- SummarySheet now accepts onNavigate callback
- KPI cards now link to appropriate sheets:
  - Solved Tariff, Solar Y1 CUF, Wind Y1 PLF -> Generation
  - Equity IRR, Project IRR, LCOE, Payback -> IRR / Returns
  - Min DSCR, Avg DSCR, Total Capex, Debt -> Debt
  - IDC -> IDC / Phasing
- WorkbookView accepts optional onNavigate prop
- Scenario page passes onNavigate to switch active sheet
This commit is contained in:
Manohar Gupta 2026-05-22 17:57:32 +05:30
parent 5098bf86f4
commit 680df1a597
3 changed files with 92 additions and 41 deletions

View file

@ -249,6 +249,7 @@ export default function ScenarioPage() {
return inputs?.wind?.capacity_mw || inputs?.project?.capacity_wind_mw || 0; return inputs?.wind?.capacity_mw || inputs?.project?.capacity_wind_mw || 0;
} catch { return 0; } } catch { return 0; }
})()} })()}
onNavigate={(sheet) => setActiveSheet(sheet as ActiveSheet)}
/> />
) : scenario?.status === "failed" ? ( ) : scenario?.status === "failed" ? (
<div className="border border-red-200 rounded-lg p-6 text-sm max-w-lg"> <div className="border border-red-200 rounded-lg p-6 text-sm max-w-lg">

View file

@ -3,12 +3,14 @@ interface KpiCardProps {
value: string | null; value: string | null;
unit?: string; unit?: string;
highlight?: boolean; highlight?: boolean;
onClick?: () => void;
} }
export function KpiCard({ label, value, unit, highlight }: KpiCardProps) { export function KpiCard({ label, value, unit, highlight, onClick }: KpiCardProps) {
return ( return (
<div <div
className={`border rounded-lg p-4 flex flex-col gap-1 ${highlight ? "border-primary/40 bg-primary/5" : ""}`} onClick={onClick}
className={`border rounded-lg p-4 flex flex-col gap-1 transition-all cursor-pointer hover:border-primary/50 hover:bg-primary/5 ${highlight ? "border-primary/40 bg-primary/5" : ""} ${onClick ? "cursor-pointer" : ""}`}
> >
<span className="text-xs text-muted-foreground font-medium uppercase tracking-wide"> <span className="text-xs text-muted-foreground font-medium uppercase tracking-wide">
{label} {label}

View file

@ -298,7 +298,7 @@ function buildDebtRows(debt: DebtYearRow[]): TableRow[] {
// Sheet views // Sheet views
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function SummarySheet({ kpis, scenarioId }: { kpis: KpiSummary; scenarioId: string }) { function SummarySheet({ kpis, scenarioId, onNavigate }: { kpis: KpiSummary; scenarioId: string; onNavigate?: (sheet: string) => void }) {
const { data: stmts } = useQuery({ const { data: stmts } = useQuery({
queryKey: ["statements", scenarioId], queryKey: ["statements", scenarioId],
queryFn: () => getStatements(scenarioId), queryFn: () => getStatements(scenarioId),
@ -364,25 +364,72 @@ function SummarySheet({ kpis, scenarioId }: { kpis: KpiSummary; scenarioId: stri
value={kpis.solved_tariff_inr_per_kwh != null ? kpis.solved_tariff_inr_per_kwh.toFixed(2) : null} value={kpis.solved_tariff_inr_per_kwh != null ? kpis.solved_tariff_inr_per_kwh.toFixed(2) : null}
unit="₹/kWh" unit="₹/kWh"
highlight highlight
onClick={() => onNavigate?.("generation")}
/> />
<KpiCard <KpiCard
label="Equity IRR" label="Equity IRR"
value={kpis.equity_irr != null ? `${(kpis.equity_irr * 100).toFixed(1)}%` : null} value={kpis.equity_irr != null ? `${(kpis.equity_irr * 100).toFixed(1)}%` : null}
highlight highlight
onClick={() => onNavigate?.("irr")}
/>
<KpiCard
label="Project IRR"
value={kpis.project_irr != null ? `${(kpis.project_irr * 100).toFixed(1)}%` : null}
onClick={() => onNavigate?.("irr")}
/>
<KpiCard
label="Min DSCR"
value={kpis.min_dscr?.toFixed(2) ?? null}
onClick={() => onNavigate?.("debt")}
/>
<KpiCard
label="Avg DSCR"
value={kpis.avg_dscr?.toFixed(2) ?? null}
onClick={() => onNavigate?.("debt")}
/>
<KpiCard
label="Total Capex"
value={kpis.total_capex_cr != null ? kpis.total_capex_cr.toFixed(1) : null}
unit="Cr"
onClick={() => onNavigate?.("debt")}
/>
<KpiCard
label="Debt"
value={kpis.debt_cr?.toFixed(1) ?? null}
unit="Cr"
onClick={() => onNavigate?.("debt")}
/>
<KpiCard
label="IDC"
value={kpis.idc_cr?.toFixed(1) ?? null}
unit="Cr"
onClick={() => onNavigate?.("idc")}
/>
<KpiCard
label="LCOE"
value={kpis.lcoe_inr_per_kwh?.toFixed(2) ?? null}
unit="₹/kWh"
onClick={() => onNavigate?.("irr")}
/>
<KpiCard
label="Payback"
value={kpis.payback_years?.toFixed(1) ?? null}
unit="yrs"
onClick={() => onNavigate?.("irr")}
/> />
<KpiCard label="Project IRR" value={kpis.project_irr != null ? `${(kpis.project_irr * 100).toFixed(1)}%` : null} />
<KpiCard label="Min DSCR" value={kpis.min_dscr?.toFixed(2) ?? null} />
<KpiCard label="Avg DSCR" value={kpis.avg_dscr?.toFixed(2) ?? null} />
<KpiCard label="Total Capex" value={kpis.total_capex_cr != null ? kpis.total_capex_cr.toFixed(1) : null} unit="Cr" />
<KpiCard label="Debt" value={kpis.debt_cr?.toFixed(1) ?? null} unit="Cr" />
<KpiCard label="IDC" value={kpis.idc_cr?.toFixed(1) ?? null} unit="Cr" />
<KpiCard label="LCOE" value={kpis.lcoe_inr_per_kwh?.toFixed(2) ?? null} unit="₹/kWh" />
<KpiCard label="Payback" value={kpis.payback_years?.toFixed(1) ?? null} unit="yrs" />
{kpis.solar_y1_cuf != null && ( {kpis.solar_y1_cuf != null && (
<KpiCard label="Solar Y1 CUF" value={`${(kpis.solar_y1_cuf * 100).toFixed(1)}%`} /> <KpiCard
label="Solar Y1 CUF"
value={`${(kpis.solar_y1_cuf * 100).toFixed(1)}%`}
onClick={() => onNavigate?.("generation")}
/>
)} )}
{kpis.wind_y1_plf != null && ( {kpis.wind_y1_plf != null && (
<KpiCard label="Wind Y1 PLF" value={`${(kpis.wind_y1_plf * 100).toFixed(1)}%`} /> <KpiCard
label="Wind Y1 PLF"
value={`${(kpis.wind_y1_plf * 100).toFixed(1)}%`}
onClick={() => onNavigate?.("generation")}
/>
)} )}
{kpis.rtc_cuf_achieved != null && ( {kpis.rtc_cuf_achieved != null && (
<KpiCard label="RTC CUF" value={`${(kpis.rtc_cuf_achieved * 100).toFixed(1)}%`} highlight /> <KpiCard label="RTC CUF" value={`${(kpis.rtc_cuf_achieved * 100).toFixed(1)}%`} highlight />
@ -997,9 +1044,10 @@ interface Props {
codYear?: number; codYear?: number;
solarDCMW?: number; solarDCMW?: number;
windMW?: number; windMW?: number;
onNavigate?: (sheet: string) => void;
} }
export function WorkbookView({ scenarioId, kpis, debtScheduleJson, activeSheet, codYear, solarDCMW, windMW }: Props) { export function WorkbookView({ scenarioId, kpis, debtScheduleJson, activeSheet, codYear, solarDCMW, windMW, onNavigate }: Props) {
const { data: stmts } = useQuery({ const { data: stmts } = useQuery({
queryKey: ["statements", scenarioId], queryKey: ["statements", scenarioId],
queryFn: () => getStatements(scenarioId), queryFn: () => getStatements(scenarioId),
@ -1032,7 +1080,7 @@ export function WorkbookView({ scenarioId, kpis, debtScheduleJson, activeSheet,
All monetary values in INR Crore unless noted All monetary values in INR Crore unless noted
</p> </p>
{activeSheet === "summary" && <SummarySheet kpis={kpis} scenarioId={scenarioId} />} {activeSheet === "summary" && <SummarySheet kpis={kpis} scenarioId={scenarioId} onNavigate={onNavigate} />}
{activeSheet === "pnl" && pnl.length > 0 && ( {activeSheet === "pnl" && pnl.length > 0 && (
<HorizontalTable years={years} rows={buildPnLRows(pnl)} /> <HorizontalTable years={years} rows={buildPnLRows(pnl)} />
)} )}