Remodel/packages/web/app/page.tsx
Manohar Gupta 112c25c26b
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
Add CUF profile viewer with upload capability
2026-05-23 17:22:31 +05:30

184 lines
5.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import {
createScenario,
listScenarios,
archiveScenario,
type Scenario,
type ScenarioInputPayload,
} from "@/lib/api";
import { Button } from "@/components/ui/button";
import { ScenarioWizard } from "@/components/ScenarioWizard";
import { ProfileViewer } from "@/components/ProfileViewer";
function StatusBadge({ status }: { status: string }) {
const styles: Record<string, string> = {
success: "bg-green-100 text-green-700",
failed: "bg-red-100 text-red-700",
running: "bg-blue-100 text-blue-700",
queued: "bg-yellow-100 text-yellow-700",
};
return (
<span
className={`px-2 py-0.5 rounded-full text-xs font-medium capitalize ${styles[status] ?? "bg-muted text-muted-foreground"}`}
>
{status}
</span>
);
}
function ScenarioRow({
scenario,
onArchive,
}: {
scenario: Scenario;
onArchive: (id: string) => void;
}) {
const router = useRouter();
const kpis = scenario.kpis_json
? (() => {
try {
return JSON.parse(scenario.kpis_json) as Record<string, number | null>;
} catch {
return null;
}
})()
: null;
const tariff = kpis?.solved_tariff_inr_per_kwh;
const irr = kpis?.equity_irr;
return (
<tr
className="border-b hover:bg-muted/30 cursor-pointer text-sm"
onClick={() => router.push(`/scenarios/${scenario.id}`)}
>
<td className="py-3 px-4 font-medium">{scenario.name}</td>
<td className="py-3 px-4">
<StatusBadge status={scenario.status} />
</td>
<td className="py-3 px-4 tabular-nums">
{tariff != null ? `${tariff.toFixed(2)}/kWh` : "—"}
</td>
<td className="py-3 px-4 tabular-nums">
{irr != null ? `${(irr * 100).toFixed(1)}%` : "—"}
</td>
<td className="py-3 px-4 text-muted-foreground">
{new Date(scenario.created_at).toLocaleString()}
</td>
<td
className="py-3 px-4 text-right"
onClick={(e) => e.stopPropagation()}
>
<Button
variant="ghost"
size="sm"
onClick={() => onArchive(scenario.id)}
className="text-muted-foreground hover:text-red-600"
>
Archive
</Button>
</td>
</tr>
);
}
export default function HomePage() {
const router = useRouter();
const [showWizard, setShowWizard] = useState(false);
const { data: scenarios, refetch } = useQuery({
queryKey: ["scenarios"],
queryFn: listScenarios,
refetchInterval: 5000,
});
async function handleWizardSubmit(
name: string,
inputs: ScenarioInputPayload,
) {
const scenario = await createScenario(name, inputs);
await refetch();
router.push(`/scenarios/${scenario.id}`);
}
async function handleArchive(id: string) {
await archiveScenario(id);
await refetch();
}
if (showWizard) {
return (
<main className="flex-1 container mx-auto px-4 py-8 max-w-2xl">
<div className="border rounded-xl p-8 shadow-sm">
<ScenarioWizard
onSubmit={handleWizardSubmit}
onCancel={() => setShowWizard(false)}
/>
</div>
</main>
);
}
return (
<main className="flex-1 container mx-auto px-4 py-8 max-w-5xl">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-2xl font-bold tracking-tight">REmodel</h1>
<p className="text-muted-foreground mt-1">
Hybrid RE project finance Solar + Wind + BESS
</p>
</div>
<div className="flex gap-2">
<ProfileViewer />
<Button variant="outline" onClick={() => router.push("/compare")}>Compare</Button>
<Button onClick={() => setShowWizard(true)}>+ New Scenario</Button>
</div>
</div>
{!scenarios || scenarios.length === 0 ? (
<div className="text-center text-muted-foreground py-24 border rounded-lg">
No scenarios yet click &ldquo;New Scenario&rdquo; to start.
</div>
) : (
<div className="border rounded-lg overflow-hidden">
<table className="w-full">
<thead className="bg-muted/50">
<tr>
<th className="py-2 px-4 text-left text-xs font-medium text-muted-foreground uppercase">
Name
</th>
<th className="py-2 px-4 text-left text-xs font-medium text-muted-foreground uppercase">
Status
</th>
<th className="py-2 px-4 text-left text-xs font-medium text-muted-foreground uppercase">
Tariff
</th>
<th className="py-2 px-4 text-left text-xs font-medium text-muted-foreground uppercase">
Equity IRR
</th>
<th className="py-2 px-4 text-left text-xs font-medium text-muted-foreground uppercase">
Created
</th>
<th />
</tr>
</thead>
<tbody>
{scenarios.map((s) => (
<ScenarioRow
key={s.id}
scenario={s}
onArchive={handleArchive}
/>
))}
</tbody>
</table>
</div>
)}
</main>
);
}