"use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import type { Assessment, AssessmentSummary } from "@/lib/types"; import { dateLabel, money } from "@/lib/format"; import { Empty } from "@/components/charts"; import LoadError from "@/components/LoadError"; import { errorMessage, getJson, sendJson } from "@/lib/client"; export default function Home() { const router = useRouter(); const [rows, setRows] = useState(null); const [name, setName] = useState(""); const [busy, setBusy] = useState(false); const [err, setErr] = useState(""); const [loadError, setLoadError] = useState(""); const fileRef = useRef(null); const load = useCallback(async () => { setLoadError(""); try { setRows(await getJson("/api/assessments")); } catch (e) { setLoadError(errorMessage(e)); } }, []); useEffect(() => { load(); }, [load]); /** Only navigate once the server has confirmed the new id. */ async function open(created: Assessment) { if (!created?.id) throw new Error("The server did not return an assessment id."); router.push(`/assessments/${created.id}`); } async function create(e: React.FormEvent) { e.preventDefault(); if (!name.trim() || busy) return; setBusy(true); setErr(""); try { await open(await sendJson("/api/assessments", "POST", { customerName: name.trim() })); } catch (e) { setErr(errorMessage(e)); setBusy(false); } } async function importFile(f: File) { setBusy(true); setErr(""); try { const parsed = JSON.parse(await f.text()); await open(await sendJson("/api/assessments", "POST", { import: parsed })); } catch (e) { setErr(f.name.endsWith(".json") ? errorMessage(e) : `${f.name} is not a JSON assessment file.`); setBusy(false); } } async function remove(id: string, label: string) { if (!confirm(`Delete the assessment for ${label}? The JSON file is removed from data/assessments.`)) return; try { await sendJson(`/api/assessments/${id}`, "DELETE"); load(); } catch (e) { setErr(errorMessage(e)); } } const totalSavings = (rows ?? []).reduce((s, r) => s + r.annualSavings, 0); return (

Assessments

Capture a customer’s incumbent tool landscape, map it onto our portfolio, and build the consolidation business case. Every assessment is a portable JSON file under data/assessments/.

{rows && rows.length > 0 && (
Pipeline savings identified
{money(totalSavings)}/yr
)}
setName(e.target.value)} />
{ const f = e.target.files?.[0]; if (f) importFile(f); e.target.value = ""; }} />
{err && (
{err}
)} {loadError ? ( ) : rows === null ? (
Loading…
) : rows.length === 0 ? ( No assessments yet — name a customer above to start one. ) : (
{rows.map((r) => (

{r.customerName}

{r.candidateCount} tools
Spend assessed
{money(r.annualSpend, true)}
Annual saving
{money(r.annualSavings, true)}
{r.preparedBy ? `${r.preparedBy} · ` : ""}Updated {dateLabel(r.updatedAt)}
Report
))}
)}
); }